/*
Copyright 2008-2010 Gephi
Authors : Mathieu Bastian <mathieu.bastian@gephi.org>
Website : http://www.gephi.org
This file is part of Gephi.
Gephi is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Gephi is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Gephi. If not, see <http://www.gnu.org/licenses/>.
*/
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();
}
}
}