package com.vitco.low.hull; import com.threed.jpct.SimpleVector; import com.vitco.low.CubeIndexer; import com.vitco.low.triangulate.util.Grid2PolyHelper; import com.vitco.settings.VitcoSettings; import gnu.trove.iterator.TIntIterator; import gnu.trove.map.hash.TIntObjectHashMap; import gnu.trove.set.hash.TIntHashSet; import java.io.Serializable; import java.util.ArrayList; import java.util.HashSet; import java.util.Set; /** * Efficient way to compute the hull for a group of * objects in 3D space (with short values as coordinates) */ public class HullManager<T> implements HullManagerInterface<T>, Serializable { private static final long serialVersionUID = 1L; // -------------- // maps position to objects private final TIntObjectHashMap<T> id2obj = new TIntObjectHashMap<T>(); // border private final TIntHashSet[] border = new TIntHashSet[]{ new TIntHashSet(),new TIntHashSet(),new TIntHashSet(), new TIntHashSet(),new TIntHashSet(),new TIntHashSet() }; // border changes @SuppressWarnings("unchecked") private final TIntObjectHashMap<T>[] borderAdded = new TIntObjectHashMap[]{ new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>(), new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>() }; @SuppressWarnings("unchecked") private final TIntObjectHashMap<T>[] borderRemoved = new TIntObjectHashMap[]{ new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>(), new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>() }; // add/remove buffer @SuppressWarnings("unchecked") private final TIntObjectHashMap<T>[] borderBufferAdded = new TIntObjectHashMap[]{ new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>(), new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>() }; @SuppressWarnings("unchecked") private final TIntObjectHashMap<T>[] borderBufferRemoved = new TIntObjectHashMap[]{ new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>(), new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>(),new TIntObjectHashMap<T>() }; // --------------------- @Override public final void clear() { id2obj.clear(); for (int i = 0; i < 6; i++) { border[i].clear(); borderAdded[i].clear(); borderRemoved[i].clear(); borderBufferAdded[i].clear(); borderBufferRemoved[i].clear(); } } @Override public final boolean contains(short[] pos) { return id2obj.containsKey(CubeIndexer.getId(pos)); } @Override public final boolean contains(int posId) { return id2obj.containsKey(posId); } @Override public final boolean containsBorder(short[] pos, int orientation) { return border[orientation].contains(CubeIndexer.getId(pos)); } @Override public final boolean containsBorder(int posId, int orientation) { return border[orientation].contains(posId); } @Override public final int[] getPosIds() { return id2obj.keys(); } @Override public T get(short[] pos) { return id2obj.get(CubeIndexer.getId(pos)); } @Override public final void update(short[] pos, T object) { //System.out.println("U " + pos[0] + "," + pos[1] + "," + pos[2]); update(CubeIndexer.getId(pos), object); } @Override public final void update(int posId, T object) { // store the object if (id2obj.put(posId, object) != null) { // the element was only updated (but existed already) T obj = id2obj.get(posId); for (int i = 0; i < 6; i++) { if (border[i].contains(posId)) { // this does not use the buffer, because the side was not actually // added this run and we want to allow for a potential remove (!) borderAdded[i].put(posId, obj); } // also update the buffers if (borderBufferAdded[i].containsKey(posId)) { borderBufferAdded[i].put(posId, obj); } if (borderBufferRemoved[i].containsKey(posId)) { borderBufferRemoved[i].put(posId, obj); } if (borderRemoved[i].containsKey(posId)) { borderRemoved[i].put(posId, obj); } } } else { T obj = id2obj.get(posId); // check borders int idOff = posId-1; if (id2obj.containsKey(idOff)) { border[0].remove(idOff); if (null == borderBufferAdded[0].remove(idOff)) { borderRemoved[0].put(idOff, id2obj.get(idOff)); } else { borderAdded[0].remove(idOff); } } else { border[1].add(posId); if (null != borderBufferAdded[1].put(posId, obj)) { borderAdded[1].put(posId, obj); } } // check borders idOff = posId+1; if (id2obj.containsKey(idOff)) { border[1].remove(idOff); if (null == borderBufferAdded[1].remove(idOff)) { borderRemoved[1].put(idOff, id2obj.get(idOff)); } else { borderAdded[1].remove(idOff); } } else { border[0].add(posId); if (null != borderBufferAdded[0].put(posId, obj)) { borderAdded[0].put(posId, obj); } } // check borders idOff = posId-CubeIndexer.widthwidth; if (id2obj.containsKey(idOff)) { border[2].remove(idOff); if (null == borderBufferAdded[2].remove(idOff)) { borderRemoved[2].put(idOff, id2obj.get(idOff)); } else { borderAdded[2].remove(idOff); } } else { border[3].add(posId); if (null != borderBufferAdded[3].put(posId, obj)) { borderAdded[3].put(posId, obj); } } // check borders idOff = posId+CubeIndexer.widthwidth; if (id2obj.containsKey(idOff)) { border[3].remove(idOff); if (null == borderBufferAdded[3].remove(idOff)) { borderRemoved[3].put(idOff, id2obj.get(idOff)); } else { borderAdded[3].remove(idOff); } } else { border[2].add(posId); if (null != borderBufferAdded[2].put(posId, obj)) { borderAdded[2].put(posId, obj); } } // check borders idOff = posId-CubeIndexer.width; if (id2obj.containsKey(idOff)) { border[4].remove(idOff); if (null == borderBufferAdded[4].remove(idOff)) { borderRemoved[4].put(idOff, id2obj.get(idOff)); } else { borderAdded[4].remove(idOff); } } else { border[5].add(posId); if (null != borderBufferAdded[5].put(posId, obj)) { borderAdded[5].put(posId, obj); } } // check borders idOff = posId+CubeIndexer.width; if (id2obj.containsKey(idOff)) { border[5].remove(idOff); if (null == borderBufferAdded[5].remove(idOff)) { borderRemoved[5].put(idOff, id2obj.get(idOff)); } else { borderAdded[5].remove(idOff); } } else { border[4].add(posId); if (null != borderBufferAdded[4].put(posId, obj)) { borderAdded[4].put(posId, obj); } } } } @Override public final boolean clearPosition(short[] pos) { //System.out.println("C " + pos[0] + "," + pos[1] + "," + pos[2]); return clearPosition(CubeIndexer.getId(pos)); } @Override public final boolean clearPosition(int posId) { // remove the object (the actual removal needs to be done // last, because we still need the reference to the object if (id2obj.containsKey(posId)) { T obj = id2obj.get(posId); T objOff; // check borders int idOff = posId-1; if (id2obj.containsKey(idOff)) { border[0].add(idOff); objOff = id2obj.get(idOff); if (null != borderBufferRemoved[0].put(idOff, objOff)) { borderAdded[0].put(idOff, objOff); } } else { border[1].remove(posId); if (null == borderBufferRemoved[1].remove(posId)) { borderRemoved[1].put(posId, obj); } else { borderAdded[1].remove(posId); } } // check borders idOff = posId+1; if (id2obj.containsKey(idOff)) { border[1].add(idOff); objOff = id2obj.get(idOff); if (null != borderBufferRemoved[1].put(idOff, objOff)) { borderAdded[1].put(idOff, objOff); } } else { border[0].remove(posId); if (null == borderBufferRemoved[0].remove(posId)) { borderRemoved[0].put(posId, obj); } else { borderAdded[0].remove(posId); } } // check borders idOff = posId-CubeIndexer.widthwidth; if (id2obj.containsKey(idOff)) { border[2].add(idOff); objOff = id2obj.get(idOff); if (null != borderBufferRemoved[2].put(idOff, objOff)) { borderAdded[2].put(idOff, objOff); } } else { border[3].remove(posId); if (null == borderBufferRemoved[3].remove(posId)) { borderRemoved[3].put(posId, obj); } else { borderAdded[3].remove(posId); } } // check borders idOff = posId+CubeIndexer.widthwidth; if (id2obj.containsKey(idOff)) { border[3].add(idOff); objOff = id2obj.get(idOff); if (null != borderBufferRemoved[3].put(idOff, objOff)) { borderAdded[3].put(idOff, objOff); } } else { border[2].remove(posId); if (null == borderBufferRemoved[2].remove(posId)) { borderRemoved[2].put(posId, obj); } else { borderAdded[2].remove(posId); } } // check borders idOff = posId-CubeIndexer.width; if (id2obj.containsKey(idOff)) { border[4].add(idOff); objOff = id2obj.get(idOff); if (null != borderBufferRemoved[4].put(idOff, objOff)) { borderAdded[4].put(idOff, objOff); } } else { border[5].remove(posId); if (null == borderBufferRemoved[5].remove(posId)) { borderRemoved[5].put(posId, obj); } else { borderAdded[5].remove(posId); } } // check borders idOff = posId+CubeIndexer.width; if (id2obj.containsKey(idOff)) { border[5].add(idOff); objOff = id2obj.get(idOff); if (null != borderBufferRemoved[5].put(idOff, objOff)) { borderAdded[5].put(idOff, objOff); } } else { border[4].remove(posId); if (null == borderBufferRemoved[4].remove(posId)) { borderRemoved[4].put(posId, obj); } else { borderAdded[4].remove(posId); } } // remove the object id2obj.remove(posId); return true; } return false; } @Override public final Set<T> getHullAdditions(int direction) { // add pending changes borderAdded[direction].putAll(borderBufferAdded[direction]); borderAdded[direction].putAll(borderBufferRemoved[direction]); // remove the values that are pending as remove (remove is stronger!) for (TIntIterator it = borderRemoved[direction].keySet().iterator(); it.hasNext();) { borderAdded[direction].remove(it.next()); } // generate result Set<T> result = new HashSet<T>(borderAdded[direction].valueCollection()); // clear buffer and changes borderBufferAdded[direction].clear(); borderBufferRemoved[direction].clear(); borderAdded[direction].clear(); return result; } @Override public final Set<T> getHullRemovals(int direction) { // generate result Set<T> result = new HashSet<T>(borderRemoved[direction].valueCollection()); // remove the values that are pending as remove (remove is stronger!) for (TIntIterator it = borderRemoved[direction].keySet().iterator(); it.hasNext();) { borderAdded[direction].remove(it.next()); } // clear buffer and changes borderRemoved[direction].clear(); return result; } // get the current hull @Override public final short[][] getHull(int direction) { short[][] result = new short[border[direction].size()][3]; // allocate with correct size int count = 0; for (TIntIterator it = border[direction].iterator(); it.hasNext();) { short[] val = CubeIndexer.getPos(it.next()); result[count][0] = val[0]; result[count][1] = val[1]; result[count][2] = val[2]; count++; } return result; } // get the visible voxel ids @Override public final TIntHashSet getVisibleVoxelsIds() { TIntHashSet visibleVoxels = new TIntHashSet(); for (int i = 0; i < 6; i++) { visibleVoxels.addAll(border[i]); } return visibleVoxels; } // get the current hull as ids @Override public final int[] getHullAsIds(int direction) { return border[direction].toArray(); } // get the outline of all voxels into one direction @Override public SimpleVector[][] getOutline(int side) { // compute the correct orientation (w.r.t. the side) int orientation = side == 0 ? 5 : (side == 1 ? 3 : 1); if (!border[orientation].isEmpty()) { // find correct id mappings int id1, id2; switch (side) { case 0: id1 = 0; id2 = 1; break; case 1: id1 = 0; id2 = 2; break; default: id1 = 1; id2 = 2; break; } // find minimum and range of the data short minX = Short.MAX_VALUE; short maxX = Short.MIN_VALUE; short minY = Short.MAX_VALUE; short maxY = Short.MIN_VALUE; short[][] voxelPositions = this.getHull(orientation); for (short[] pos : voxelPositions) { minX = (short) Math.min(minX, pos[id1]); maxX = (short) Math.max(maxX, pos[id1]); minY = (short) Math.min(minY, pos[id2]); maxY = (short) Math.max(maxY, pos[id2]); } // convert to boolean array boolean[][] data = new boolean[maxX - minX + 1][maxY - minY + 1]; for (short[] pos : voxelPositions) { data[pos[id1] - minX][pos[id2] - minY] = true; } // convert to polygon short[][][] polys = Grid2PolyHelper.convert(data); // translate outline ArrayList<SimpleVector[]> lines = new ArrayList<SimpleVector[]>(); for (short[][] poly : polys) { for (short[] outline : poly) { SimpleVector p1, p2; switch (side) { case 2: p1 = new SimpleVector(0, minX + outline[0], minY + outline[1]); break; case 1: p1 = new SimpleVector(minX + outline[0], 0, minY + outline[1]); break; default: p1 = new SimpleVector(minX + outline[0], minY + outline[1], 0); break; } p1.scalarMul(VitcoSettings.VOXEL_SIZE); p1.sub(VitcoSettings.VOXEL_WORLD_OFFSET); for (int i = 2; i < outline.length;) { switch (side) { case 2: p2 = new SimpleVector(0, minX + outline[i++], minY + outline[i++]); break; case 1: p2 = new SimpleVector(minX + outline[i++], 0, minY + outline[i++]); break; default: p2 = new SimpleVector(minX + outline[i++], minY + outline[i++], 0); break; } p2.scalarMul(VitcoSettings.VOXEL_SIZE); p2.sub(VitcoSettings.VOXEL_WORLD_OFFSET); lines.add(new SimpleVector[]{p1, p2}); p1 = p2; } } } // generate result array SimpleVector[][] result = new SimpleVector[lines.size()][]; lines.toArray(result); return result; } return new SimpleVector[0][]; } }