/* Copyright 2008-2010 Gephi Authors : Mathieu Bastian <mathieu.bastian@gephi.org> Website : http://www.gephi.org This file is part of Gephi. DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. Copyright 2011 Gephi Consortium. All rights reserved. The contents of this file are subject to the terms of either the GNU General Public License Version 3 only ("GPL") or the Common Development and Distribution License("CDDL") (collectively, the "License"). You may not use this file except in compliance with the License. You can obtain a copy of the License at http://gephi.org/about/legal/license-notice/ or /cddl-1.0.txt and /gpl-3.0.txt. See the License for the specific language governing permissions and limitations under the License. When distributing the software, include this License Header Notice in each file and include the License files at /cddl-1.0.txt and /gpl-3.0.txt. If applicable, add the following below the License Header, with the fields enclosed by brackets [] replaced by your own identifying information: "Portions Copyrighted [year] [name of copyright owner]" If you wish your version of this file to be governed by only the CDDL or only the GPL Version 3, indicate your decision by adding "[Contributor] elects to include this software in this distribution under the [CDDL or GPL Version 3] license." If you do not indicate a single choice of license, a recipient has the option to distribute your version of this file under either the CDDL, the GPL Version 3 or to extend the choice of license to its licensees as provided above. However, if you add GPL Version 3 code and therefore, elected the GPL Version 3 license, then the option applies only if the new code is made subject to such option by the copyright holder. Contributor(s): Portions Copyrighted 2011 Gephi Consortium. */ package org.gephi.visualization.opengl.octree; import com.sun.opengl.util.BufferUtil; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import javax.media.opengl.GL; import javax.media.opengl.glu.GLU; import org.gephi.utils.collection.avl.ResetableIterator; import org.gephi.utils.collection.avl.AVLItemAccessor; import org.gephi.utils.collection.avl.ParamAVLIterator; import org.gephi.utils.collection.avl.ParamAVLTree; import org.gephi.visualization.GraphLimits; import org.gephi.visualization.VizArchitecture; import org.gephi.visualization.VizController; import org.gephi.visualization.opengl.AbstractEngine; import org.gephi.visualization.apiimpl.ModelImpl; import org.gephi.lib.gleem.linalg.Vec3f; import org.gephi.visualization.swing.GraphDrawableImpl; /** * * @author Mathieu Bastian */ public class Octree implements VizArchitecture { //Architecture private GraphDrawableImpl drawable; private AbstractEngine engine; private GraphLimits limits; protected VizController vizController; //Attributes private int modelIDs; private int maxDepth; private int classesCount; private int size; private int cacheMarker; //Octant protected Octant root; private ParamAVLTree<Octant> leaves; //Iterator private ConcurrentLinkedQueue<OctreeIterator> iteratorQueue; //States protected List<Octant> visibleLeaves; protected List<Octant> selectedLeaves; //Utils protected ParamAVLIterator<ModelImpl> updatePositionIterator = new ParamAVLIterator<ModelImpl>(); protected ParamAVLIterator<ModelImpl> cleanObjectsIterator = new ParamAVLIterator<ModelImpl>(); public Octree(int maxDepth, int size, int nbClasses) { this.maxDepth = maxDepth; this.classesCount = nbClasses; this.size = size; } public void initArchitecture() { this.engine = VizController.getInstance().getEngine(); this.drawable = VizController.getInstance().getDrawable(); this.limits = VizController.getInstance().getLimits(); this.vizController = VizController.getInstance(); leaves = new ParamAVLTree<Octant>(new AVLItemAccessor<Octant>() { public int getNumber(Octant item) { return item.getNumber(); } }); visibleLeaves = new ArrayList<Octant>(); selectedLeaves = new ArrayList<Octant>(); iteratorQueue = new ConcurrentLinkedQueue<OctreeIterator>(); iteratorQueue.add(new OctreeIterator()); iteratorQueue.add(new OctreeIterator()); iteratorQueue.add(new OctreeIterator()); iteratorQueue.add(new OctreeIterator()); float dis = size / (float) Math.pow(2, this.maxDepth + 1); root = new Octant(this, 0, dis, dis, dis, size); } public void addObject(int classID, ModelImpl obj) { Octant[] octants = obj.getOctants(); boolean manualAdd = true; for (int i = 0; i < octants.length; i++) { Octant o = octants[i]; if (o != null) { o.addObject(classID, obj); manualAdd = false; } } if (manualAdd) { root.addObject(classID, obj); } } public void removeObject(int classID, ModelImpl obj) { Octant[] octants = obj.getOctants(); for (int i = 0; i < octants.length; i++) { Octant o = obj.getOctants()[i]; if (o != null) { octants[i].removeObject(classID, obj); } } } public void updateVisibleOctant(GL gl) { //Limits refreshLimits(); //Switch to OpenGL select mode int capacity = 1 * 4 * leaves.getCount(); //Each object take in maximium : 4 * name stack depth IntBuffer hitsBuffer = BufferUtil.newIntBuffer(capacity); gl.glSelectBuffer(hitsBuffer.capacity(), hitsBuffer); gl.glRenderMode(GL.GL_SELECT); gl.glInitNames(); gl.glPushName(0); gl.glDisable(GL.GL_CULL_FACE); //Disable flags //Draw the nodes cube in the select buffer for (Octant n : leaves) { n.resetUpdatePositionFlag(); //Profit from the loop to do this, because this method is always after updating position gl.glLoadName(n.getNumber()); n.displayOctant(gl); } int nbRecords = gl.glRenderMode(GL.GL_RENDER); if (vizController.getVizModel().isCulling()) { gl.glEnable(GL.GL_CULL_FACE); gl.glCullFace(GL.GL_BACK); } visibleLeaves.clear(); //Get the hits and add the nodes' objects to the array int depth = Integer.MAX_VALUE; int minDepth = -1; for (int i = 0; i < nbRecords; i++) { int hit = hitsBuffer.get(i * 4 + 3); //-1 Because of the glPushName(0) int minZ = hitsBuffer.get(i * 4 + 1); if (minZ < depth) { depth = minZ; minDepth = hit; } Octant nodeHit = leaves.getItem(hit); visibleLeaves.add(nodeHit); } if (minDepth != -1) { Octant closestOctant = leaves.getItem(minDepth); Vec3f pos = new Vec3f(closestOctant.getPosX(), closestOctant.getPosY(), closestOctant.getPosZ()); limits.setClosestPoint(pos); } //System.out.println(minDepth); } public void updateSelectedOctant(GL gl, GLU glu, float[] mousePosition, float[] pickRectangle) { //Start Picking mode int capacity = 1 * 4 * visibleLeaves.size(); //Each object take in maximium : 4 * name stack depth IntBuffer hitsBuffer = BufferUtil.newIntBuffer(capacity); gl.glSelectBuffer(hitsBuffer.capacity(), hitsBuffer); gl.glRenderMode(GL.GL_SELECT); gl.glDisable(GL.GL_CULL_FACE); //Disable flags gl.glInitNames(); gl.glPushName(0); gl.glMatrixMode(GL.GL_PROJECTION); gl.glPushMatrix(); gl.glLoadIdentity(); glu.gluPickMatrix(mousePosition[0], mousePosition[1], pickRectangle[0], pickRectangle[1], drawable.getViewport()); gl.glMultMatrixd(drawable.getProjectionMatrix()); gl.glMatrixMode(GL.GL_MODELVIEW); //Draw the nodes' cube int the select buffer int hitName = 1; for (int i = 0; i < visibleLeaves.size(); i++) { Octant node = visibleLeaves.get(i); gl.glLoadName(hitName); node.displayOctant(gl); hitName++; } //Restoring the original projection matrix gl.glMatrixMode(GL.GL_PROJECTION); gl.glPopMatrix(); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glFlush(); //Returning to normal rendering mode int nbRecords = gl.glRenderMode(GL.GL_RENDER); if (vizController.getVizModel().isCulling()) { gl.glEnable(GL.GL_CULL_FACE); gl.glCullFace(GL.GL_BACK); } //Clean previous selection selectedLeaves.clear(); //Get the hits and put the node under selection in the selectionArray for (int i = 0; i < nbRecords; i++) { int hit = hitsBuffer.get(i * 4 + 3) - 1; //-1 Because of the glPushName(0) Octant nodeHit = visibleLeaves.get(hit); selectedLeaves.add(nodeHit); } } public void cleanDeletedObjects(int classID) { for (Octant o : leaves) { for (cleanObjectsIterator.setNode(o.getTree(classID)); cleanObjectsIterator.hasNext();) { ModelImpl obj = cleanObjectsIterator.next(); if (!obj.isCacheMatching(cacheMarker)) { removeObject(classID, obj); obj.resetOctant(); if (vizController.getVizConfig().isCleanDeletedModels()) { obj.cleanModel(); } } } } } public void resetObjectClass(int classID) { for (Octant o : leaves) { ParamAVLTree<ModelImpl> tree = o.getTree(classID); //Reset octants in objects for (cleanObjectsIterator.setNode(tree); cleanObjectsIterator.hasNext();) { ModelImpl obj = cleanObjectsIterator.next(); obj.resetOctant(); obj.cleanModel(); obj.destroy(); } //Empty the tree o.clear(classID); } } public void updateObjectsPosition(int classID) { for (Octant o : leaves) { if (o.isRequiringUpdatePosition()) { for (updatePositionIterator.setNode(o.getTree(classID)); updatePositionIterator.hasNext();) { ModelImpl obj = updatePositionIterator.next(); if (!obj.isInOctreeLeaf(o)) { o.removeObject(classID, obj); obj.resetOctant(); addObject(classID, obj); //TODO break the loop somehow } } } } } public Iterator<ModelImpl> getObjectIterator(int classID) { OctreeIterator itr = borrowIterator(); itr.reset(visibleLeaves, classID); return itr; } public Iterator<ModelImpl> getSelectedObjectIterator(int classID) { OctreeIterator itr = borrowIterator(); itr.reset(selectedLeaves, classID); return itr; } public int countSelectedObjects(int classID) { int res = 0; for (int i = 0; i < selectedLeaves.size(); i++) { Octant o = selectedLeaves.get(i); res += o.getTree(classID).getCount(); } return res; } public void displayOctree(GL gl, GLU glu) { gl.glDisable(GL.GL_CULL_FACE); gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE); for (Octant o : visibleLeaves) { gl.glColor3f(1, 0.5f, 0.5f); o.displayOctant(gl); o.displayOctantInfo(gl, glu); } if (!vizController.getVizConfig().isWireFrame()) { gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL); } if (vizController.getVizModel().isCulling()) { gl.glEnable(GL.GL_CULL_FACE); gl.glCullFace(GL.GL_BACK); } } void addLeaf(Octant leaf) { leaves.add(leaf); } void removeLeaf(Octant leaf) { leaves.remove(leaf); } private void refreshLimits() { float minX = Float.POSITIVE_INFINITY; float maxX = Float.NEGATIVE_INFINITY; float minY = Float.POSITIVE_INFINITY; float maxY = Float.NEGATIVE_INFINITY; float minZ = Float.POSITIVE_INFINITY; float maxZ = Float.NEGATIVE_INFINITY; for (Octant o : leaves) { float octanSize = o.getSize() / 2f; minX = Math.min(minX, o.getPosX() - octanSize); maxX = Math.max(maxX, o.getPosX() + octanSize); minY = Math.min(minY, o.getPosY() - octanSize); maxY = Math.max(maxY, o.getPosY() + octanSize); minZ = Math.min(minZ, o.getPosZ() - octanSize); maxZ = Math.max(maxZ, o.getPosZ() + octanSize); } int viewportMinX = Integer.MAX_VALUE; int viewportMaxX = Integer.MIN_VALUE; int viewportMinY = Integer.MAX_VALUE; int viewportMaxY = Integer.MIN_VALUE; double[] point; point = drawable.myGluProject(minX, minY, minZ); //bottom far left viewportMinX = Math.min(viewportMinX, (int) point[0]); viewportMinY = Math.min(viewportMinY, (int) point[1]); viewportMaxX = Math.max(viewportMaxX, (int) point[0]); viewportMaxY = Math.max(viewportMaxY, (int) point[1]); point = drawable.myGluProject(minX, minY, maxZ); //bottom near left viewportMinX = Math.min(viewportMinX, (int) point[0]); viewportMinY = Math.min(viewportMinY, (int) point[1]); viewportMaxX = Math.max(viewportMaxX, (int) point[0]); viewportMaxY = Math.max(viewportMaxY, (int) point[1]); point = drawable.myGluProject(minX, maxY, maxZ); //up near left viewportMinX = Math.min(viewportMinX, (int) point[0]); viewportMinY = Math.min(viewportMinY, (int) point[1]); viewportMaxX = Math.max(viewportMaxX, (int) point[0]); viewportMaxY = Math.max(viewportMaxY, (int) point[1]); point = drawable.myGluProject(maxX, minY, maxZ); //bottom near right viewportMinX = Math.min(viewportMinX, (int) point[0]); viewportMinY = Math.min(viewportMinY, (int) point[1]); viewportMaxX = Math.max(viewportMaxX, (int) point[0]); viewportMaxY = Math.max(viewportMaxY, (int) point[1]); point = drawable.myGluProject(maxX, minY, minZ); //bottom far right viewportMinX = Math.min(viewportMinX, (int) point[0]); viewportMinY = Math.min(viewportMinY, (int) point[1]); viewportMaxX = Math.max(viewportMaxX, (int) point[0]); viewportMaxY = Math.max(viewportMaxY, (int) point[1]); point = drawable.myGluProject(maxX, maxY, minZ); //up far right viewportMinX = Math.min(viewportMinX, (int) point[0]); viewportMinY = Math.min(viewportMinY, (int) point[1]); viewportMaxX = Math.max(viewportMaxX, (int) point[0]); viewportMaxY = Math.max(viewportMaxY, (int) point[1]); point = drawable.myGluProject(maxX, maxY, maxZ); //up near right viewportMinX = Math.min(viewportMinX, (int) point[0]); viewportMinY = Math.min(viewportMinY, (int) point[1]); viewportMaxX = Math.max(viewportMaxX, (int) point[0]); viewportMaxY = Math.max(viewportMaxY, (int) point[1]); point = drawable.myGluProject(minX, maxY, minZ); //up far left viewportMinX = Math.min(viewportMinX, (int) point[0]); viewportMinY = Math.min(viewportMinY, (int) point[1]); viewportMaxX = Math.max(viewportMaxX, (int) point[0]); viewportMaxY = Math.max(viewportMaxY, (int) point[1]); limits.setMinXoctree(minX); limits.setMaxXoctree(maxX); limits.setMinYoctree(minY); limits.setMaxYoctree(maxY); limits.setMinZoctree(minZ); limits.setMaxZoctree(maxZ); limits.setMinXviewport(viewportMinX); limits.setMaxXviewport(viewportMaxX); limits.setMinYviewport(viewportMinY); limits.setMaxYviewport(viewportMaxY); } int getClassesCount() { return classesCount; } int getMaxDepth() { return maxDepth; } int getNextObjectID() { return modelIDs++; } private OctreeIterator borrowIterator() { OctreeIterator itr = iteratorQueue.poll(); if (itr == null) { System.err.println("Octree iterator starved"); } return itr; } private void returnIterator(OctreeIterator iterator) { iteratorQueue.add(iterator); } public void setCacheMarker(int cacheMarker) { this.cacheMarker = cacheMarker; } private class OctreeIterator implements Iterator<ModelImpl>, ResetableIterator { private int i = 0; private int classID; private List<Octant> octants; private ParamAVLIterator<ModelImpl> octantIterator; public OctreeIterator() { octantIterator = new ParamAVLIterator<ModelImpl>(); } public OctreeIterator(List<Octant> octants, int classID) { this(); this.octants = octants; this.classID = classID; } public void reset(List<Octant> octants, int classID) { this.octants = octants; this.classID = classID; i = 0; } public boolean hasNext() { if (!octantIterator.hasNext()) { while (i < octants.size()) { octantIterator.setNode(octants.get(i).getTree(classID)); i++; if (octantIterator.hasNext()) { return true; } } returnIterator(this); return false; } return true; } public ModelImpl next() { ModelImpl obj = octantIterator.next(); return obj; } public void remove() { octantIterator.remove(); } } }