From 3dc0f0d01bc390a563b7580d0f7659cc7cf9108c Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Thu, 8 Dec 2022 23:11:50 +0100 Subject: [PATCH] Cleanup --- .../diylc/serialization/FontConverter.java | 6 +- .../diylc-core/src/org/diylc/utils/RTree.java | 1240 +++++++++-------- 2 files changed, 626 insertions(+), 620 deletions(-) diff --git a/diylc/diylc-core/src/org/diylc/serialization/FontConverter.java b/diylc/diylc-core/src/org/diylc/serialization/FontConverter.java index 7c639686..17dea794 100644 --- a/diylc/diylc-core/src/org/diylc/serialization/FontConverter.java +++ b/diylc/diylc-core/src/org/diylc/serialization/FontConverter.java @@ -32,7 +32,11 @@ import com.thoughtworks.xstream.io.HierarchicalStreamWriter; * * @author Branislav Stojkovic */ -public class FontConverter extends com.thoughtworks.xstream.converters.extended.FontConverter { +public class FontConverter extends com.thoughtworks.xstream.converters.extended.FontConverter { + + public FontConverter() { + super(null); + } @Override public void marshal(Object object, HierarchicalStreamWriter writer, MarshallingContext context) { diff --git a/diylc/diylc-core/src/org/diylc/utils/RTree.java b/diylc/diylc-core/src/org/diylc/utils/RTree.java index d6df484e..abbe83a5 100644 --- a/diylc/diylc-core/src/org/diylc/utils/RTree.java +++ b/diylc/diylc-core/src/org/diylc/utils/RTree.java @@ -13,666 +13,668 @@ import java.util.Queue; import java.util.Set; /** - * Implementation of an arbitrary-dimension RTree. Based on R-Trees: A Dynamic - * Index Structure for Spatial Searching (Antonn Guttmann, 1984) + * Implementation of an arbitrary-dimension RTree. Based on R-Trees: A Dynamic Index Structure for + * Spatial Searching (Antonn Guttmann, 1984) *

* This class is not thread-safe. *

- * Copyright 2010 Russ Weeks rweeks@newbrightidea.com Licensed under the GNU - * LGPL License details here: http://www.gnu.org/licenses/lgpl-3.0.txt + * Copyright 2010 Russ Weeks rweeks@newbrightidea.com Licensed under the GNU LGPL License details + * here: http://www.gnu.org/licenses/lgpl-3.0.txt * * @param the type of entry to store in this RTree. */ public class RTree { - public enum SeedPicker {LINEAR, QUADRATIC} + public enum SeedPicker { + LINEAR, QUADRATIC + } - private final int maxEntries; - private final int minEntries; - private final int numDims; + private final int maxEntries; + private final int minEntries; + private final int numDims; - private final float[] pointDims; + private final float[] pointDims; - private final SeedPicker seedPicker; + private final SeedPicker seedPicker; - private Node root; + private Node root; - private volatile int size; + private volatile int size; - /** - * Creates a new RTree. - * - * @param maxEntries maximum number of entries per node - * @param minEntries minimum number of entries per node (except for the root node) - * @param numDims the number of dimensions of the RTree. - */ - public RTree(int maxEntries, int minEntries, int numDims, SeedPicker seedPicker) { - assert (minEntries <= (maxEntries / 2)); - this.numDims = numDims; - this.maxEntries = maxEntries; - this.minEntries = minEntries; - this.seedPicker = seedPicker; - pointDims = new float[numDims]; - root = buildRoot(true); + /** + * Creates a new RTree. + * + * @param maxEntries maximum number of entries per node + * @param minEntries minimum number of entries per node (except for the root node) + * @param numDims the number of dimensions of the RTree. + */ + public RTree(int maxEntries, int minEntries, int numDims, SeedPicker seedPicker) { + assert (minEntries <= (maxEntries / 2)); + this.numDims = numDims; + this.maxEntries = maxEntries; + this.minEntries = minEntries; + this.seedPicker = seedPicker; + pointDims = new float[numDims]; + root = buildRoot(true); + } + + public RTree(int maxEntries, int minEntries, int numDims) { + this(maxEntries, minEntries, numDims, SeedPicker.LINEAR); + } + + private Node buildRoot(boolean asLeaf) { + float[] initCoords = new float[numDims]; + float[] initDimensions = new float[numDims]; + for (int i = 0; i < this.numDims; i++) { + initCoords[i] = (float) Math.sqrt(Float.MAX_VALUE); + initDimensions[i] = -2.0f * (float) Math.sqrt(Float.MAX_VALUE); } + return new Node(initCoords, initDimensions, asLeaf); + } - public RTree(int maxEntries, int minEntries, int numDims) { - this(maxEntries, minEntries, numDims, SeedPicker.LINEAR); - } + /** + * Builds a new RTree using default parameters: maximum 50 entries per node minimum 2 entries per + * node 2 dimensions + */ + public RTree() { + this(50, 2, 2, SeedPicker.LINEAR); + } - private Node buildRoot(boolean asLeaf) { - float[] initCoords = new float[numDims]; - float[] initDimensions = new float[numDims]; - for (int i = 0; i < this.numDims; i++) { - initCoords[i] = (float) Math.sqrt(Float.MAX_VALUE); - initDimensions[i] = -2.0f * (float) Math.sqrt(Float.MAX_VALUE); + /** + * @return the maximum number of entries per node + */ + public int getMaxEntries() { + return maxEntries; + } + + /** + * @return the minimum number of entries per node for all nodes except the root. + */ + public int getMinEntries() { + return minEntries; + } + + /** + * @return the number of dimensions of the tree + */ + public int getNumDims() { + return numDims; + } + + /** + * @return the number of items in this tree. + */ + public int size() { + return size; + } + + /** + * Searches the RTree for objects overlapping with the given rectangle. + * + * @param coords the corner of the rectangle that is the lower bound of every dimension (eg. the + * top-left corner) + * @param dimensions the dimensions of the rectangle. + * @return a list of objects whose rectangles overlap with the given rectangle. + */ + public List search(float[] coords, float[] dimensions) { + assert (coords.length == numDims); + assert (dimensions.length == numDims); + LinkedList results = new LinkedList(); + search(coords, dimensions, root, results); + return results; + } + + public List search(Area area) { + Rectangle2D bounds2D = area.getBounds2D(); + return search(new float[] {(float) bounds2D.getMinX(), (float) bounds2D.getMinY()}, + new float[] {(float) bounds2D.getWidth(), (float) bounds2D.getHeight()}); + } + + public List search(Point2D point, double t) { + return search(new float[] {(float) (point.getX() - t / 2), (float) (point.getY() - t / 2)}, + new float[] {(float) t, (float) t}); + } + + @SuppressWarnings("unchecked") + private void search(float[] coords, float[] dimensions, Node n, LinkedList results) { + if (n.leaf) { + for (Node e : n.children) { + if (isOverlap(coords, dimensions, e.coords, e.dimensions)) { + results.add(((Entry) e).entry); } - return new Node(initCoords, initDimensions, asLeaf); - } - - /** - * Builds a new RTree using default parameters: maximum 50 entries per node - * minimum 2 entries per node 2 dimensions - */ - public RTree() { - this(50, 2, 2, SeedPicker.LINEAR); - } - - /** - * @return the maximum number of entries per node - */ - public int getMaxEntries() { - return maxEntries; - } - - /** - * @return the minimum number of entries per node for all nodes except the - * root. - */ - public int getMinEntries() { - return minEntries; - } - - /** - * @return the number of dimensions of the tree - */ - public int getNumDims() { - return numDims; - } - - /** - * @return the number of items in this tree. - */ - public int size() { - return size; - } - - /** - * Searches the RTree for objects overlapping with the given rectangle. - * - * @param coords the corner of the rectangle that is the lower bound of every - * dimension (eg. the top-left corner) - * @param dimensions the dimensions of the rectangle. - * @return a list of objects whose rectangles overlap with the given - * rectangle. - */ - public List search(float[] coords, float[] dimensions) { - assert (coords.length == numDims); - assert (dimensions.length == numDims); - LinkedList results = new LinkedList(); - search(coords, dimensions, root, results); - return results; - } - - public List search(Area area) { - Rectangle2D bounds2D = area.getBounds2D(); - return search(new float[] { (float) bounds2D.getMinX(), (float) bounds2D.getMinY() } , new float[] { (float) bounds2D.getWidth(), (float) bounds2D.getHeight() }); - } - - public List search(Point2D point, double t) { - return search(new float[] { (float) (point.getX() - t / 2), (float) (point.getY() - t / 2) }, - new float[] { (float) t, (float) t }); - } - - private void search(float[] coords, float[] dimensions, Node n, - LinkedList results) { - if (n.leaf) { - for (Node e : n.children) { - if (isOverlap(coords, dimensions, e.coords, e.dimensions)) { - results.add(((Entry) e).entry); - } - } - } else { - for (Node c : n.children) { - if (isOverlap(coords, dimensions, c.coords, c.dimensions)) { - search(coords, dimensions, c, results); - } - } + } + } else { + for (Node c : n.children) { + if (isOverlap(coords, dimensions, c.coords, c.dimensions)) { + search(coords, dimensions, c, results); } + } } + } - /** - * Deletes the entry associated with the given rectangle from the RTree - * - * @param coords the corner of the rectangle that is the lower bound in every - * dimension - * @param dimensions the dimensions of the rectangle - * @param entry the entry to delete - * @return true iff the entry was deleted from the RTree. - */ - public boolean delete(float[] coords, float[] dimensions, T entry) { - assert (coords.length == numDims); - assert (dimensions.length == numDims); - Node l = findLeaf(root, coords, dimensions, entry); - if (l == null) { - System.out.println("WTF?"); - findLeaf(root, coords, dimensions, entry); - } - assert (l != null) : "Could not find leaf for entry to delete"; - assert (l.leaf) : "Entry is not found at leaf?!?"; - ListIterator li = l.children.listIterator(); - T removed = null; - while (li.hasNext()) { - @SuppressWarnings("unchecked") - Entry e = (Entry) li.next(); - if (e.entry.equals(entry)) { - removed = e.entry; - li.remove(); - break; - } - } - if (removed != null) { - condenseTree(l); - size--; - } - if (size == 0) { - root = buildRoot(true); - } - return (removed != null); + /** + * Deletes the entry associated with the given rectangle from the RTree + * + * @param coords the corner of the rectangle that is the lower bound in every dimension + * @param dimensions the dimensions of the rectangle + * @param entry the entry to delete + * @return true iff the entry was deleted from the RTree. + */ + public boolean delete(float[] coords, float[] dimensions, T entry) { + assert (coords.length == numDims); + assert (dimensions.length == numDims); + Node l = findLeaf(root, coords, dimensions, entry); + if (l == null) { + System.out.println("WTF?"); + findLeaf(root, coords, dimensions, entry); } + assert (l != null) : "Could not find leaf for entry to delete"; + assert (l.leaf) : "Entry is not found at leaf?!?"; + ListIterator li = l.children.listIterator(); + T removed = null; + while (li.hasNext()) { + @SuppressWarnings("unchecked") + Entry e = (Entry) li.next(); + if (e.entry.equals(entry)) { + removed = e.entry; + li.remove(); + break; + } + } + if (removed != null) { + condenseTree(l); + size--; + } + if (size == 0) { + root = buildRoot(true); + } + return (removed != null); + } - public boolean delete(float[] coords, T entry) { - return delete(coords, pointDims, entry); - } + public boolean delete(float[] coords, T entry) { + return delete(coords, pointDims, entry); + } - private Node findLeaf(Node n, float[] coords, float[] dimensions, T entry) { - if (n.leaf) { - for (Node c : n.children) { - if (((Entry) c).entry.equals(entry)) { - return n; - } - } - return null; - } else { - for (Node c : n.children) { - if (isOverlap(c.coords, c.dimensions, coords, dimensions)) { - Node result = findLeaf(c, coords, dimensions, entry); - if (result != null) { - return result; - } - } - } - return null; + @SuppressWarnings("unchecked") + private Node findLeaf(Node n, float[] coords, float[] dimensions, T entry) { + if (n.leaf) { + for (Node c : n.children) { + if (((Entry) c).entry.equals(entry)) { + return n; } - } - - private void condenseTree(Node n) { - Set q = new HashSet(); - while (n != root) { - if (n.leaf && (n.children.size() < minEntries)) { - q.addAll(n.children); - n.parent.children.remove(n); - } else if (!n.leaf && (n.children.size() < minEntries)) { - // probably a more efficient way to do this... - LinkedList toVisit = new LinkedList(n.children); - while (!toVisit.isEmpty()) { - Node c = toVisit.pop(); - if (c.leaf) { - q.addAll(c.children); - } else { - toVisit.addAll(c.children); - } - } - n.parent.children.remove(n); - } else { - tighten(n); - } - n = n.parent; + } + return null; + } else { + for (Node c : n.children) { + if (isOverlap(c.coords, c.dimensions, coords, dimensions)) { + Node result = findLeaf(c, coords, dimensions, entry); + if (result != null) { + return result; + } } - if (root.children.size() == 0) { - root = buildRoot(true); - } else if ((root.children.size() == 1) && (!root.leaf)) { - root = root.children.get(0); - root.parent = null; - } else { - tighten(root); - } - for (Node ne : q) { - @SuppressWarnings("unchecked") - Entry e = (Entry) ne; - insert(e.coords, e.dimensions, e.entry); - } - size -= q.size(); + } + return null; } + } - /** - * Empties the RTree - */ - public void clear() { - root = buildRoot(true); - // let the GC take care of the rest. - } - - /** - * Inserts the given entry into the RTree, associated with the given - * rectangle. - * - * @param coords the corner of the rectangle that is the lower bound in every - * dimension - * @param dimensions the dimensions of the rectangle - * @param entry the entry to insert - */ - public void insert(float[] coords, float[] dimensions, T entry) { - assert (coords.length == numDims); - assert (dimensions.length == numDims); - Entry e = new Entry(coords, dimensions, entry); - Node l = chooseLeaf(root, e); - l.children.add(e); - size++; - e.parent = l; - if (l.children.size() > maxEntries) { - Node[] splits = splitNode(l); - adjustTree(splits[0], splits[1]); - } else { - adjustTree(l, null); - } - } - - public void insert(Area a, T entry) { - Rectangle2D bounds2d = a.getBounds2D(); - insert(new float[] {(float) bounds2d.getMinX(), (float) bounds2d.getMinY()}, - new float[] {(float) bounds2d.getWidth(), (float) bounds2d.getHeight()}, entry); - } - - public void insert(Point2D p, double t, T entry) { - insert(new float[] {(float) (p.getX() - t / 2), (float) (p.getY() - t /2) }, - new float[] {(float) t, (float) t }, entry); - } - - /** - * Convenience method for inserting a point - * - * @param coords - * @param entry - */ - public void insert(float[] coords, T entry) { - insert(coords, pointDims, entry); - } - - public void visit(NodeVisitor nv) { - float[] coordBuf = new float[numDims]; - float[] dimBuf = new float[numDims]; - Queue toVisit = new LinkedList<>(); - Map nodeDepths = new HashMap<>(); - nodeDepths.put(root, 0); - toVisit.add(root); + private void condenseTree(Node n) { + Set q = new HashSet(); + while (n != root) { + if (n.leaf && (n.children.size() < minEntries)) { + q.addAll(n.children); + n.parent.children.remove(n); + } else if (!n.leaf && (n.children.size() < minEntries)) { + // probably a more efficient way to do this... + LinkedList toVisit = new LinkedList(n.children); while (!toVisit.isEmpty()) { - Node currentNode = toVisit.remove(); - if (currentNode.parent != null) { - nodeDepths.put(currentNode, nodeDepths.get(currentNode.parent) + 1); - } - System.arraycopy(currentNode.coords, 0, coordBuf, 0, numDims); - System.arraycopy(currentNode.dimensions, 0, dimBuf, 0, numDims); - if (currentNode instanceof RTree.Entry) { - nv.visit(nodeDepths.get(currentNode), coordBuf, dimBuf, ((Entry)currentNode).entry); - } else { - nv.visit(nodeDepths.get(currentNode), coordBuf, dimBuf, null); - } - toVisit.addAll(currentNode.children); - } - } - - public interface NodeVisitor { - public void visit(int depth, float[] coords, float[] dimensions, V value); - } - - private void adjustTree(Node n, Node nn) { - if (n == root) { - if (nn != null) { - // build new root and add children. - root = buildRoot(false); - root.children.add(n); - n.parent = root; - root.children.add(nn); - nn.parent = root; - } - tighten(root); - return; + Node c = toVisit.pop(); + if (c.leaf) { + q.addAll(c.children); + } else { + toVisit.addAll(c.children); + } } + n.parent.children.remove(n); + } else { tighten(n); - if (nn != null) { - tighten(nn); - if (n.parent.children.size() > maxEntries) { - Node[] splits = splitNode(n.parent); - adjustTree(splits[0], splits[1]); - } - } - if (n.parent != null) { - adjustTree(n.parent, null); - } + } + n = n.parent; } + if (root.children.size() == 0) { + root = buildRoot(true); + } else if ((root.children.size() == 1) && (!root.leaf)) { + root = root.children.get(0); + root.parent = null; + } else { + tighten(root); + } + for (Node ne : q) { + @SuppressWarnings("unchecked") + Entry e = (Entry) ne; + insert(e.coords, e.dimensions, e.entry); + } + size -= q.size(); + } - private Node[] splitNode(Node n) { - // TODO: this class probably calls "tighten" a little too often. - // For instance the call at the end of the "while (!cc.isEmpty())" loop - // could be modified and inlined because it's only adjusting for the addition - // of a single node. Left as-is for now for readability. - @SuppressWarnings("unchecked") - Node[] nn = new RTree.Node[] - {n, new Node(n.coords, n.dimensions, n.leaf)}; - nn[1].parent = n.parent; - if (nn[1].parent != null) { - nn[1].parent.children.add(nn[1]); - } - LinkedList cc = new LinkedList(n.children); - n.children.clear(); - Node[] ss = seedPicker == SeedPicker.LINEAR ? lPickSeeds(cc) : qPickSeeds(cc); - nn[0].children.add(ss[0]); - nn[1].children.add(ss[1]); - tighten(nn); - while (!cc.isEmpty()) { - if ((nn[0].children.size() >= minEntries) - && (nn[1].children.size() + cc.size() == minEntries)) { - nn[1].children.addAll(cc); - cc.clear(); - tighten(nn); // Not sure this is required. - return nn; - } else if ((nn[1].children.size() >= minEntries) - && (nn[0].children.size() + cc.size() == minEntries)) { - nn[0].children.addAll(cc); - cc.clear(); - tighten(nn); // Not sure this is required. - return nn; - } - Node c = seedPicker == SeedPicker.LINEAR ? lPickNext(cc) : qPickNext(cc, nn); - Node preferred; - float e0 = getRequiredExpansion(nn[0].coords, nn[0].dimensions, c); - float e1 = getRequiredExpansion(nn[1].coords, nn[1].dimensions, c); - if (e0 < e1) { - preferred = nn[0]; - } else if (e0 > e1) { - preferred = nn[1]; - } else { - float a0 = getArea(nn[0].dimensions); - float a1 = getArea(nn[1].dimensions); - if (a0 < a1) { - preferred = nn[0]; - } else if (e0 > a1) { - preferred = nn[1]; - } else { - if (nn[0].children.size() < nn[1].children.size()) { - preferred = nn[0]; - } else if (nn[0].children.size() > nn[1].children.size()) { - preferred = nn[1]; - } else { - preferred = nn[(int) Math.round(Math.random())]; - } - } - } - preferred.children.add(c); - tighten(preferred); - } + /** + * Empties the RTree + */ + public void clear() { + root = buildRoot(true); + // let the GC take care of the rest. + } + + /** + * Inserts the given entry into the RTree, associated with the given rectangle. + * + * @param coords the corner of the rectangle that is the lower bound in every dimension + * @param dimensions the dimensions of the rectangle + * @param entry the entry to insert + */ + public void insert(float[] coords, float[] dimensions, T entry) { + assert (coords.length == numDims); + assert (dimensions.length == numDims); + Entry e = new Entry(coords, dimensions, entry); + Node l = chooseLeaf(root, e); + l.children.add(e); + size++; + e.parent = l; + if (l.children.size() > maxEntries) { + Node[] splits = splitNode(l); + adjustTree(splits[0], splits[1]); + } else { + adjustTree(l, null); + } + } + + public void insert(Area a, T entry) { + Rectangle2D bounds2d = a.getBounds2D(); + insert(new float[] {(float) bounds2d.getMinX(), (float) bounds2d.getMinY()}, + new float[] {(float) bounds2d.getWidth(), (float) bounds2d.getHeight()}, entry); + } + + public void insert(Point2D p, double t, T entry) { + insert(new float[] {(float) (p.getX() - t / 2), (float) (p.getY() - t / 2)}, + new float[] {(float) t, (float) t}, entry); + } + + /** + * Convenience method for inserting a point + * + * @param coords + * @param entry + */ + public void insert(float[] coords, T entry) { + insert(coords, pointDims, entry); + } + + @SuppressWarnings("unchecked") + public void visit(NodeVisitor nv) { + float[] coordBuf = new float[numDims]; + float[] dimBuf = new float[numDims]; + Queue toVisit = new LinkedList<>(); + Map nodeDepths = new HashMap<>(); + nodeDepths.put(root, 0); + toVisit.add(root); + while (!toVisit.isEmpty()) { + Node currentNode = toVisit.remove(); + if (currentNode.parent != null) { + nodeDepths.put(currentNode, nodeDepths.get(currentNode.parent) + 1); + } + System.arraycopy(currentNode.coords, 0, coordBuf, 0, numDims); + System.arraycopy(currentNode.dimensions, 0, dimBuf, 0, numDims); + if (currentNode instanceof RTree.Entry) { + nv.visit(nodeDepths.get(currentNode), coordBuf, dimBuf, ((Entry) currentNode).entry); + } else { + nv.visit(nodeDepths.get(currentNode), coordBuf, dimBuf, null); + } + toVisit.addAll(currentNode.children); + } + } + + public interface NodeVisitor { + public void visit(int depth, float[] coords, float[] dimensions, V value); + } + + private void adjustTree(Node n, Node nn) { + if (n == root) { + if (nn != null) { + // build new root and add children. + root = buildRoot(false); + root.children.add(n); + n.parent = root; + root.children.add(nn); + nn.parent = root; + } + tighten(root); + return; + } + tighten(n); + if (nn != null) { + tighten(nn); + if (n.parent.children.size() > maxEntries) { + Node[] splits = splitNode(n.parent); + adjustTree(splits[0], splits[1]); + } + } + if (n.parent != null) { + adjustTree(n.parent, null); + } + } + + private Node[] splitNode(Node n) { + // TODO: this class probably calls "tighten" a little too often. + // For instance the call at the end of the "while (!cc.isEmpty())" loop + // could be modified and inlined because it's only adjusting for the addition + // of a single node. Left as-is for now for readability. + @SuppressWarnings("unchecked") + Node[] nn = new RTree.Node[] {n, new Node(n.coords, n.dimensions, n.leaf)}; + nn[1].parent = n.parent; + if (nn[1].parent != null) { + nn[1].parent.children.add(nn[1]); + } + LinkedList cc = new LinkedList(n.children); + n.children.clear(); + Node[] ss = seedPicker == SeedPicker.LINEAR ? lPickSeeds(cc) : qPickSeeds(cc); + nn[0].children.add(ss[0]); + nn[1].children.add(ss[1]); + tighten(nn); + while (!cc.isEmpty()) { + if ((nn[0].children.size() >= minEntries) + && (nn[1].children.size() + cc.size() == minEntries)) { + nn[1].children.addAll(cc); + cc.clear(); + tighten(nn); // Not sure this is required. return nn; - } - - // Implementation of Quadratic PickSeeds - private RTree.Node[] qPickSeeds(LinkedList nn) { - @SuppressWarnings("unchecked") - RTree.Node[] bestPair = new RTree.Node[2]; - float maxWaste = -1.0f * Float.MAX_VALUE; - for (Node n1 : nn) { - for (Node n2 : nn) { - if (n1 == n2) continue; - float n1a = getArea(n1.dimensions); - float n2a = getArea(n2.dimensions); - float ja = 1.0f; - for (int i = 0; i < numDims; i++) { - float jc0 = Math.min(n1.coords[i], n2.coords[i]); - float jc1 = Math.max(n1.coords[i] + n1.dimensions[i], n2.coords[i] + n2.dimensions[i]); - ja *= (jc1 - jc0); - } - float waste = ja - n1a - n2a; - if (waste > maxWaste) { - maxWaste = waste; - bestPair[0] = n1; - bestPair[1] = n2; - } - } + } else if ((nn[1].children.size() >= minEntries) + && (nn[0].children.size() + cc.size() == minEntries)) { + nn[0].children.addAll(cc); + cc.clear(); + tighten(nn); // Not sure this is required. + return nn; + } + Node c = seedPicker == SeedPicker.LINEAR ? lPickNext(cc) : qPickNext(cc, nn); + Node preferred; + float e0 = getRequiredExpansion(nn[0].coords, nn[0].dimensions, c); + float e1 = getRequiredExpansion(nn[1].coords, nn[1].dimensions, c); + if (e0 < e1) { + preferred = nn[0]; + } else if (e0 > e1) { + preferred = nn[1]; + } else { + float a0 = getArea(nn[0].dimensions); + float a1 = getArea(nn[1].dimensions); + if (a0 < a1) { + preferred = nn[0]; + } else if (e0 > a1) { + preferred = nn[1]; + } else { + if (nn[0].children.size() < nn[1].children.size()) { + preferred = nn[0]; + } else if (nn[0].children.size() > nn[1].children.size()) { + preferred = nn[1]; + } else { + preferred = nn[(int) Math.round(Math.random())]; + } } - nn.remove(bestPair[0]); - nn.remove(bestPair[1]); - return bestPair; + } + preferred.children.add(c); + tighten(preferred); } + return nn; + } - /** - * Implementation of QuadraticPickNext - * - * @param cc the children to be divided between the new nodes, one item will be removed from this list. - * @param nn the candidate nodes for the children to be added to. - */ - private Node qPickNext(LinkedList cc, Node[] nn) { - float maxDiff = -1.0f * Float.MAX_VALUE; - Node nextC = null; - for (Node c : cc) { - float n0Exp = getRequiredExpansion(nn[0].coords, nn[0].dimensions, c); - float n1Exp = getRequiredExpansion(nn[1].coords, nn[1].dimensions, c); - float diff = Math.abs(n1Exp - n0Exp); - if (diff > maxDiff) { - maxDiff = diff; - nextC = c; - } - } - assert (nextC != null) : "No node selected from qPickNext"; - cc.remove(nextC); - return nextC; - } - - // Implementation of LinearPickSeeds - private RTree.Node[] lPickSeeds(LinkedList nn) { - @SuppressWarnings("unchecked") - RTree.Node[] bestPair = new RTree.Node[2]; - boolean foundBestPair = false; - float bestSep = 0.0f; + // Implementation of Quadratic PickSeeds + private RTree.Node[] qPickSeeds(LinkedList nn) { + @SuppressWarnings("unchecked") + RTree.Node[] bestPair = new RTree.Node[2]; + float maxWaste = -1.0f * Float.MAX_VALUE; + for (Node n1 : nn) { + for (Node n2 : nn) { + if (n1 == n2) + continue; + float n1a = getArea(n1.dimensions); + float n2a = getArea(n2.dimensions); + float ja = 1.0f; for (int i = 0; i < numDims; i++) { - float dimLb = Float.MAX_VALUE, dimMinUb = Float.MAX_VALUE; - float dimUb = -1.0f * Float.MAX_VALUE, dimMaxLb = -1.0f * Float.MAX_VALUE; - Node nMaxLb = null, nMinUb = null; - for (Node n : nn) { - if (n.coords[i] < dimLb) { - dimLb = n.coords[i]; - } - if (n.dimensions[i] + n.coords[i] > dimUb) { - dimUb = n.dimensions[i] + n.coords[i]; - } - if (n.coords[i] > dimMaxLb) { - dimMaxLb = n.coords[i]; - nMaxLb = n; - } - if (n.dimensions[i] + n.coords[i] < dimMinUb) { - dimMinUb = n.dimensions[i] + n.coords[i]; - nMinUb = n; - } - } - float sep = (nMaxLb == nMinUb) ? -1.0f : - Math.abs((dimMinUb - dimMaxLb) / (dimUb - dimLb)); - if (sep >= bestSep) { - bestPair[0] = nMaxLb; - bestPair[1] = nMinUb; - bestSep = sep; - foundBestPair = true; - } + float jc0 = Math.min(n1.coords[i], n2.coords[i]); + float jc1 = Math.max(n1.coords[i] + n1.dimensions[i], n2.coords[i] + n2.dimensions[i]); + ja *= (jc1 - jc0); } - // In the degenerate case where all points are the same, the above - // algorithm does not find a best pair. Just pick the first 2 - // children. - if (!foundBestPair) { - bestPair = new RTree.Node[]{nn.get(0), nn.get(1)}; + float waste = ja - n1a - n2a; + if (waste > maxWaste) { + maxWaste = waste; + bestPair[0] = n1; + bestPair[1] = n2; } - nn.remove(bestPair[0]); - nn.remove(bestPair[1]); - return bestPair; + } + } + nn.remove(bestPair[0]); + nn.remove(bestPair[1]); + return bestPair; + } + + /** + * Implementation of QuadraticPickNext + * + * @param cc the children to be divided between the new nodes, one item will be removed from this + * list. + * @param nn the candidate nodes for the children to be added to. + */ + private Node qPickNext(LinkedList cc, Node[] nn) { + float maxDiff = -1.0f * Float.MAX_VALUE; + Node nextC = null; + for (Node c : cc) { + float n0Exp = getRequiredExpansion(nn[0].coords, nn[0].dimensions, c); + float n1Exp = getRequiredExpansion(nn[1].coords, nn[1].dimensions, c); + float diff = Math.abs(n1Exp - n0Exp); + if (diff > maxDiff) { + maxDiff = diff; + nextC = c; + } + } + assert (nextC != null) : "No node selected from qPickNext"; + cc.remove(nextC); + return nextC; + } + + // Implementation of LinearPickSeeds + @SuppressWarnings("unchecked") + private RTree.Node[] lPickSeeds(LinkedList nn) { + RTree.Node[] bestPair = new RTree.Node[2]; + boolean foundBestPair = false; + float bestSep = 0.0f; + for (int i = 0; i < numDims; i++) { + float dimLb = Float.MAX_VALUE, dimMinUb = Float.MAX_VALUE; + float dimUb = -1.0f * Float.MAX_VALUE, dimMaxLb = -1.0f * Float.MAX_VALUE; + Node nMaxLb = null, nMinUb = null; + for (Node n : nn) { + if (n.coords[i] < dimLb) { + dimLb = n.coords[i]; + } + if (n.dimensions[i] + n.coords[i] > dimUb) { + dimUb = n.dimensions[i] + n.coords[i]; + } + if (n.coords[i] > dimMaxLb) { + dimMaxLb = n.coords[i]; + nMaxLb = n; + } + if (n.dimensions[i] + n.coords[i] < dimMinUb) { + dimMinUb = n.dimensions[i] + n.coords[i]; + nMinUb = n; + } + } + float sep = (nMaxLb == nMinUb) ? -1.0f : Math.abs((dimMinUb - dimMaxLb) / (dimUb - dimLb)); + if (sep >= bestSep) { + bestPair[0] = nMaxLb; + bestPair[1] = nMinUb; + bestSep = sep; + foundBestPair = true; + } + } + // In the degenerate case where all points are the same, the above + // algorithm does not find a best pair. Just pick the first 2 + // children. + if (!foundBestPair) { + bestPair = new RTree.Node[] {nn.get(0), nn.get(1)}; + } + nn.remove(bestPair[0]); + nn.remove(bestPair[1]); + return bestPair; + } + + /** + * Implementation of LinearPickNext + * + * @param cc the children to be divided between the new nodes, one item will be removed from this + * list. + */ + private Node lPickNext(LinkedList cc) { + return cc.pop(); + } + + private void tighten(Node[] nn) { + for (Node n : nn) + tighten(n); + } + + private void tighten(Node n) { + assert (n.children.size() > 0) : "tighten() called on empty node!"; + float[] minCoords = new float[numDims]; + float[] maxCoords = new float[numDims]; + for (int i = 0; i < numDims; i++) { + minCoords[i] = Float.MAX_VALUE; + maxCoords[i] = -1.0f * Float.MAX_VALUE; + + for (Node c : n.children) { + // we may have bulk-added a bunch of children to a node (eg. in + // splitNode) + // so here we just enforce the child->parent relationship. + c.parent = n; + if (c.coords[i] < minCoords[i]) { + minCoords[i] = c.coords[i]; + } + if ((c.coords[i] + c.dimensions[i]) > maxCoords[i]) { + maxCoords[i] = (c.coords[i] + c.dimensions[i]); + } + } + } + for (int i = 0; i < numDims; i++) { + // Convert max coords to dimensions + maxCoords[i] -= minCoords[i]; + } + System.arraycopy(minCoords, 0, n.coords, 0, numDims); + System.arraycopy(maxCoords, 0, n.dimensions, 0, numDims); + } + + private RTree.Node chooseLeaf(RTree.Node n, RTree.Entry e) { + if (n.leaf) { + return n; + } + float minInc = Float.MAX_VALUE; + Node next = null; + for (RTree.Node c : n.children) { + float inc = getRequiredExpansion(c.coords, c.dimensions, e); + if (inc < minInc) { + minInc = inc; + next = c; + } else if (inc == minInc) { + float curArea = 1.0f; + float thisArea = 1.0f; + for (int i = 0; i < c.dimensions.length; i++) { + curArea *= next.dimensions[i]; + thisArea *= c.dimensions[i]; + } + if (thisArea < curArea) { + next = c; + } + } + } + return chooseLeaf(next, e); + } + + /** + * Returns the increase in area necessary for the given rectangle to cover the given entry. + */ + private float getRequiredExpansion(float[] coords, float[] dimensions, Node e) { + float area = getArea(dimensions); + float[] deltas = new float[dimensions.length]; + for (int i = 0; i < deltas.length; i++) { + if (coords[i] + dimensions[i] < e.coords[i] + e.dimensions[i]) { + deltas[i] = e.coords[i] + e.dimensions[i] - coords[i] - dimensions[i]; + } else if (coords[i] + dimensions[i] > e.coords[i] + e.dimensions[i]) { + deltas[i] = coords[i] - e.coords[i]; + } + } + float expanded = 1.0f; + for (int i = 0; i < dimensions.length; i++) { + expanded *= dimensions[i] + deltas[i]; + } + return (expanded - area); + } + + private float getArea(float[] dimensions) { + float area = 1.0f; + for (int i = 0; i < dimensions.length; i++) { + area *= dimensions[i]; + } + return area; + } + + private boolean isOverlap(float[] scoords, float[] sdimensions, float[] coords, + float[] dimensions) { + final float FUDGE_FACTOR = 1.001f; + for (int i = 0; i < scoords.length; i++) { + boolean overlapInThisDimension = false; + if (scoords[i] == coords[i]) { + overlapInThisDimension = true; + } else if (scoords[i] < coords[i]) { + if (scoords[i] + FUDGE_FACTOR * sdimensions[i] >= coords[i]) { + overlapInThisDimension = true; + } + } else if (scoords[i] > coords[i]) { + if (coords[i] + FUDGE_FACTOR * dimensions[i] >= scoords[i]) { + overlapInThisDimension = true; + } + } + if (!overlapInThisDimension) { + return false; + } + } + return true; + } + + private class Node { + final float[] coords; + final float[] dimensions; + final LinkedList children; + final boolean leaf; + + Node parent; + + private Node(float[] coords, float[] dimensions, boolean leaf) { + this.coords = new float[coords.length]; + this.dimensions = new float[dimensions.length]; + System.arraycopy(coords, 0, this.coords, 0, coords.length); + System.arraycopy(dimensions, 0, this.dimensions, 0, dimensions.length); + this.leaf = leaf; + children = new LinkedList(); } - /** - * Implementation of LinearPickNext - * - * @param cc the children to be divided between the new nodes, one item will be removed from this list. - */ - private Node lPickNext(LinkedList cc) { - return cc.pop(); + } + + private class Entry extends Node { + final T entry; + + public Entry(float[] coords, float[] dimensions, T entry) { + // an entry isn't actually a leaf (its parent is a leaf) + // but all the algorithms should stop at the first leaf they encounter, + // so this little hack shouldn't be a problem. + super(coords, dimensions, true); + this.entry = entry; } - private void tighten(Node... nodes) { - assert (nodes.length >= 1) : "Pass some nodes to tighten!"; - for (Node n : nodes) { - assert (n.children.size() > 0) : "tighten() called on empty node!"; - float[] minCoords = new float[numDims]; - float[] maxCoords = new float[numDims]; - for (int i = 0; i < numDims; i++) { - minCoords[i] = Float.MAX_VALUE; - maxCoords[i] = -1.0f * Float.MAX_VALUE; - - for (Node c : n.children) { - // we may have bulk-added a bunch of children to a node (eg. in - // splitNode) - // so here we just enforce the child->parent relationship. - c.parent = n; - if (c.coords[i] < minCoords[i]) { - minCoords[i] = c.coords[i]; - } - if ((c.coords[i] + c.dimensions[i]) > maxCoords[i]) { - maxCoords[i] = (c.coords[i] + c.dimensions[i]); - } - } - } - for (int i = 0; i < numDims; i++) { - // Convert max coords to dimensions - maxCoords[i] -= minCoords[i]; - } - System.arraycopy(minCoords, 0, n.coords, 0, numDims); - System.arraycopy(maxCoords, 0, n.dimensions, 0, numDims); - } + public String toString() { + return "Entry: " + entry; } - - private RTree.Node chooseLeaf(RTree.Node n, RTree.Entry e) { - if (n.leaf) { - return n; - } - float minInc = Float.MAX_VALUE; - Node next = null; - for (RTree.Node c : n.children) { - float inc = getRequiredExpansion(c.coords, c.dimensions, e); - if (inc < minInc) { - minInc = inc; - next = c; - } else if (inc == minInc) { - float curArea = 1.0f; - float thisArea = 1.0f; - for (int i = 0; i < c.dimensions.length; i++) { - curArea *= next.dimensions[i]; - thisArea *= c.dimensions[i]; - } - if (thisArea < curArea) { - next = c; - } - } - } - return chooseLeaf(next, e); - } - - /** - * Returns the increase in area necessary for the given rectangle to cover the - * given entry. - */ - private float getRequiredExpansion(float[] coords, float[] dimensions, Node e) { - float area = getArea(dimensions); - float[] deltas = new float[dimensions.length]; - for (int i = 0; i < deltas.length; i++) { - if (coords[i] + dimensions[i] < e.coords[i] + e.dimensions[i]) { - deltas[i] = e.coords[i] + e.dimensions[i] - coords[i] - dimensions[i]; - } else if (coords[i] + dimensions[i] > e.coords[i] + e.dimensions[i]) { - deltas[i] = coords[i] - e.coords[i]; - } - } - float expanded = 1.0f; - for (int i = 0; i < dimensions.length; i++) { - expanded *= dimensions[i] + deltas[i]; - } - return (expanded - area); - } - - private float getArea(float[] dimensions) { - float area = 1.0f; - for (int i = 0; i < dimensions.length; i++) { - area *= dimensions[i]; - } - return area; - } - - private boolean isOverlap(float[] scoords, float[] sdimensions, - float[] coords, float[] dimensions) { - final float FUDGE_FACTOR = 1.001f; - for (int i = 0; i < scoords.length; i++) { - boolean overlapInThisDimension = false; - if (scoords[i] == coords[i]) { - overlapInThisDimension = true; - } else if (scoords[i] < coords[i]) { - if (scoords[i] + FUDGE_FACTOR * sdimensions[i] >= coords[i]) { - overlapInThisDimension = true; - } - } else if (scoords[i] > coords[i]) { - if (coords[i] + FUDGE_FACTOR * dimensions[i] >= scoords[i]) { - overlapInThisDimension = true; - } - } - if (!overlapInThisDimension) { - return false; - } - } - return true; - } - - private class Node { - final float[] coords; - final float[] dimensions; - final LinkedList children; - final boolean leaf; - - Node parent; - - private Node(float[] coords, float[] dimensions, boolean leaf) { - this.coords = new float[coords.length]; - this.dimensions = new float[dimensions.length]; - System.arraycopy(coords, 0, this.coords, 0, coords.length); - System.arraycopy(dimensions, 0, this.dimensions, 0, dimensions.length); - this.leaf = leaf; - children = new LinkedList(); - } - - } - - private class Entry extends Node { - final T entry; - - public Entry(float[] coords, float[] dimensions, T entry) { - // an entry isn't actually a leaf (its parent is a leaf) - // but all the algorithms should stop at the first leaf they encounter, - // so this little hack shouldn't be a problem. - super(coords, dimensions, true); - this.entry = entry; - } - - public String toString() { - return "Entry: " + entry; - } - } -} \ No newline at end of file + } +}