/*
 *
 * (c) 2010
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA  02111-1307  USA
 * 
 * @author		joey scully http://cargocollective.com/josephscully
 * @modified	10/24/2010
 * @version		0.1.1
 */

import processing.opengl.*;
import javax.media.opengl.*;
import java.nio.*;

//  voltron.java version 1.1.0								//

public class voltron {
	
	PApplet p;
	
	int w,h,d;
	int i,j,k;
	byte[] data; 
	
	//these are from the java.nio library
	FloatBuffer v; 
	FloatBuffer c;
	
	//the arrays into which the bytes are put from the file
	float[] points;
	int numPoints;
	float[] cols;
	boolean updated;
	
	//variable for enabling the sectionmode
	int sectionstate = 0;
	
	GL gl;
	
	//boundaries of the volume that is to be drawn - used in the setDimens() function
	int startX;
	int startY;
	int startZ;
	int endX;
	int endY;
	int endZ;
	
	//these are the defults as the class does not require these to be predefined when called
	float alphadepth = 1;
	float threshold = 0;
	String DRAWMODE = "COLORIZE";
	
//----------------------------------------------------------------------------------------------------------------
	
	public voltron(PApplet p) {
		
		
		
		//openGL exception	
		if(!(p.g instanceof PGraphicsOpenGL)){
			throw new RuntimeException("**voltron** > This library requires OpenGL");
		}
		
		this.p=p;
		
		updated=false;
		points=new float[30000];  // these are the x,y,z coordinates
		numPoints=0;
		cols=new float[40000];  //THIS MEANS COLORS not columns
		
		
		System.out.println("**voltron** > VOLTRON v1.1.0 by Joey Scully - ker2x edition");
		
//////////////////////////////////////////////////////////////
//															//
// The data structure created - is of the form:  			//
// [x,y,z] -> [r,b,g,a] 									//
// points[1] -> cols[1]										//
// points[2] -> cols[2]										//
//															//
//////////////////////////////////////////////////////////////
		
	}

//----------------------------------------------------------------------------------------------------------------
	
	//loads the file - defines its boundaries
	public void loadVox(String fname, int ww, int hh, int dd){
		System.out.println("**voltron** > LOADING " + fname);
		
		w=ww;
		h=hh;
		d=dd;
		
		startX = 0;
		startY = 0;
		startZ = 0;
		endX = ww;
		endY = hh;
		endZ = dd;
		
		//look at file and load voxel transparencies into 'data[array of bytes]'
		data = p.loadBytes(fname);		
		
		System.out.println("**voltron** > load " + fname + " COMPLETE");
	}
	
//----------------------------------------------------------------------------------------------------------------
	
	// get voxel value at specified position
	// return 0 if outside bounds
	public int getVox(int x,int y, int z) {
		return (x>=0 && x<w && y>=0 && y<h && z>=0 && z<d) ? (int)data[(z<<16)+(y<<8)+x] : 0;
	}
	
//----------------------------------------------------------------------------------------------------------------
	
	//if this is not called dimensions are just set to max size of volume as specified in loadVox call
	public void setDimens(int sX, int sY, int sZ, int eX, int eY, int eZ){
		startX = sX;
		startY = sY;
		startZ = sZ;
		endX = eX;
		endY = eY;
		endZ = eZ;
	}
	
//----------------------------------------------------------------------------------------------------------------
	
	public void setDrawmode(float alp, float thresh, String dr){
		DRAWMODE = dr;
		alphadepth = alp/1000;
		threshold = thresh;
		
		if(DRAWMODE!="ALPHASCALE" && DRAWMODE!="COLORIZE" && DRAWMODE!="THRESHOLD"){
			throw new RuntimeException("**voltron** > DRAWMODE Variable must specify ALPHASCALE, COLORIZE or THRESHOLD");
		}
	}
	
//----------------------------------------------------------------------------------------------------------------
	
	public void setVox(int resolution, float scalar, int enable){
		
		float ALPH = 255;
		float R = 255;
		float G = 255;
		float B = 255;
		
		if(enable==1){
			
			//slices first unit/depth/Z
			for(i=startZ; i<endZ; i+=resolution){
				
				//x coordinate/width/X
				for(j=startX; j<endX; j+=resolution){
					
					//y coordinate/height/Y
					for(k=startY; k<endY; k+=resolution){
						
//----------------------------------------------------------------------------------------------------------------
//						Drawmodes
//----------------------------------------------------------------------------------------------------------------
						
						//Alpha draw mode
						if(DRAWMODE == "ALPHASCALE"){
							ALPH = getVox(i, j, k)*alphadepth; 
						}//end of alphascale
						
						
						if(DRAWMODE == "THRESHOLD"){
							ALPH = getVox(i, j, k)*alphadepth;
							if(ALPH>threshold){
								ALPH+=ALPH;
							} else {
								ALPH = 0;
							}							
						}//end of threshold
						
						
						//color draw mode
						if(DRAWMODE == "COLORIZE"){
							ALPH = getVox(i, j, k)*alphadepth;
							R = 0;
							G = 0;
							B = 0;
							
							if(ALPH>0.09){//90%+   cyan
								R = 0;
								G = 255/ALPH;
								B = 255/ALPH;
								ALPH=+ALPH;                
							}   
							
							if(ALPH>0.07 && ALPH<0.09){//70% - 90%   white
								R = 255/ALPH;
								G = 255/ALPH;
								B = 255/ALPH;
								ALPH=+ALPH;                
							}
							
							if(ALPH>0.06 && ALPH<0.07){//60% - 70%   orange
								R = 255/ALPH;
								G = 165/ALPH;
								B = 0;
								//ALPH=+ALPH;                
							}
							
							if(ALPH>0.03 && ALPH<0.06){//50% - 60%   red
								R = 255/ALPH;
								G = 0;
								B = 0;
								//ALPH=+ALPH;                
							}              
							
							if(ALPH>0.01 && ALPH<0.03){//10% - 30%  greeny
								G = 255/ALPH;                                
							}
							
							if(ALPH>0.001 && ALPH<0.01){//1% - 10%  at least a bit green
								G = 50/ALPH;                          
							}
						}//end of colorize
						
//----------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------
						
						addPoint((i*scalar)-w/2, (j*scalar)-w/2, (k*scalar)-w/2, R, G, B, ALPH);
						
					}//end of Y dimension loop
				}//end of X dimension loop 
			}//end or Z dimension loop
		}//end of render loop
		
		enable = 0;
		
		System.out.println("**voltron** > All Points Set");
		
	}//end of setVox
	
//----------------------------------------------------------------------------------------------------------------
	
	public void addPoint(float x, float y, float z, float r, float g, float b, float a)
	{  
		//adding a point - NOT A VOXEL - this is done by the containing function setVox
		updated = true;
		//once 1/3rd all operations is complete write to array
		if((numPoints+1)==points.length/3)
		{
			int len = points.length;  //this array represent the number
			len*=2;  //double the index length of the prior point array
			float[] tmp = new float[len]; 
			//'system' is a raw java class, 'arraycopy' copys aray values between defined arguments
			System.arraycopy(points, 0, tmp, 0, points.length);
			points = null;
			points = tmp;  
			
			len = cols.length;
			len*=2;
			tmp = new float[len];
			System.arraycopy(cols, 0, tmp, 0, cols.length);
			cols = null;
			cols = tmp;
			
			System.gc();//run the garbage collector
		}
		
		points[numPoints*3] = x;
		points[numPoints*3+1] = y;
		points[numPoints*3+2] = z;
		cols[numPoints*4] = r;
		cols[numPoints*4+1] = g;
		cols[numPoints*4+2] = b;
		cols[numPoints*4+3] = a;
		numPoints++;
	}

//----------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------
	
	public void plotVox(String style, float size){
		
		if(updated)
		{
			//.REORDERING THE BITES.REORDERING THE BITES .CASE_INTO_FloatBuffer
			//I believe this is using physical memory AS a floatbuffer - allowing you to do java stuff like put, rewind(), etc. on OS mem 
			v = ByteBuffer.allocateDirect(4*3*numPoints).order(ByteOrder.nativeOrder()).asFloatBuffer();
			v.put(points, 0, 3*numPoints);
			v.rewind();
			
			c = ByteBuffer.allocateDirect(4*4*numPoints).order(ByteOrder.nativeOrder()).asFloatBuffer();
			//putting brings us to the end of the memory
			c.put(cols, 0, 4*numPoints);
			//then we rewind
			c.rewind(); 
			updated = false;
		}
		
	
		
		gl=((PGraphicsOpenGL)p.g).beginGL();
		
		
		
		//just visual shit
		gl.glEnable(GL.GL_BLEND);//enable blending
		gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);//define blending as alpha blending
		gl.glDepthMask(false);//depth testing - makes depth buffer read-only
		gl.glEnable(GL.GL_POINT_SMOOTH);//antialiasing call
		gl.glHint(GL.GL_POINT_SMOOTH_HINT, GL.GL_NICEST);//best antialiasing
		gl.glPointSize(size);//specifys size of points to draw (size variable caled at method level)
		
		gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL.GL_COLOR_ARRAY);
		
		//position array
		gl.glVertexPointer(3,GL.GL_FLOAT,0,v);
		//color array   
		gl.glColorPointer(4,GL.GL_FLOAT,0,c);
		
		
	        if(style=="points"){
		  //gl.glEnable(GL.GL_POINT_SMOOTH);
		  //gl.glHint(GL.GL_POINT_SMOOTH_HINT, GL.GL_NICEST);
		  gl.glPointSize(size);
		  gl.glDrawArrays(GL.GL_POINTS,0,numPoints);
	        }
		
		gl.glDisableClientState(GL.GL_COLOR_ARRAY);
		gl.glDisableClientState(GL.GL_VERTEX_ARRAY);
		
		((PGraphicsOpenGL)p.g).endGL();
	}
	
//----------------------------------------------------------------------------------------------------------------
	
	public void plotVox(String style){
		
		if(updated)
		{
			//.REORDERING THE BITES.REORDERING THE BITES .CASE_INTO_FloatBuffer
			//I believe this is using physical memory AS a floatbuffer - allowing you to do java stuff like put, rewind(), etc. on OS mem 
			v = ByteBuffer.allocateDirect(4*3*numPoints).order(ByteOrder.nativeOrder()).asFloatBuffer();
			v.put(points, 0, 3*numPoints);
			v.rewind();
			
			c = ByteBuffer.allocateDirect(4*4*numPoints).order(ByteOrder.nativeOrder()).asFloatBuffer();
			//putting brings us to the end of the memory
			c.put(cols, 0, 4*numPoints);
			//then we rewind
			c.rewind(); 
			updated = false;
		}
		
	
		
		gl=((PGraphicsOpenGL)p.g).beginGL();
		
		
		
		//just visual shit
		gl.glEnable(GL.GL_BLEND);//enable blending
		gl.glBlendFunc(GL.GL_SRC_ALPHA,GL.GL_ONE);//define blending as alpha blending
		gl.glDepthMask(false);//depth testing - makes depth buffer read-only
		gl.glEnable(GL.GL_POINT_SMOOTH);//antialiasing call
		gl.glHint(GL.GL_POINT_SMOOTH_HINT, GL.GL_NICEST);//best antialiasing
		//gl.glPointSize(size);//specifys size of points to draw (size variable caled at method level)
		
		gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
		gl.glEnableClientState(GL.GL_COLOR_ARRAY);
		
		//position array
		gl.glVertexPointer(3,GL.GL_FLOAT,0,v);
		//color array   
		gl.glColorPointer(4,GL.GL_FLOAT,0,c);
		
	
		
	//Still pruning these styles - not all of the seem to be functioning in JOGL.	
	if(style=="poly"){
		gl.glDrawArrays(GL.GL_POLYGON,0,numPoints);
	}
	
	if(style=="trifan"){
		gl.glDrawArrays(GL.GL_TRIANGLE_FAN,0,numPoints);
	}
	if(style=="tri"){
		gl.glDrawArrays(GL.GL_TRIANGLES,0,numPoints);
	}
	if(style=="quads"){
		gl.glDrawArrays(GL.GL_QUADS,0,numPoints);
	}
	
	if(style=="quadstrip"){
		gl.glDrawArrays(GL.GL_QUAD_STRIP,0,numPoints);
	}
	
	
	if(style=="lineloop"){
		gl.glDrawArrays(GL.GL_LINE_LOOP,0,numPoints);
	}
	
	if(style=="linestrip"){
		gl.glDrawArrays(GL.GL_LINE_STRIP,0,numPoints);
	}
	
	if(style=="lines"){
		gl.glDrawArrays(GL.GL_LINES,0,numPoints);
	}
	
	if(style=="lineadj"){
		gl.glDrawArrays(GL.GL_LINES_ADJACENCY_EXT,0,numPoints);
	}

		
		gl.glDisableClientState(GL.GL_COLOR_ARRAY);
		gl.glDisableClientState(GL.GL_VERTEX_ARRAY);
		
		((PGraphicsOpenGL)p.g).endGL();
	}
	
//----------------------------------------------------------------------------------------------------------------
	
	public void clearVox(){
		//CLEARS
		v.clear();
		c.clear();
		cols = null;
		points = null;
		updated=false;
		numPoints=0;
		points=new float[30000];
		cols=new float[40000];
		
		System.out.println("**voltron** > All Points and Arrays CLEARED");
	}
	
//----------------------------------------------------------------------------------------------------------------
	
	public void sectionEnable(int enable, int slice, int depth, float size){
		if(enable==1){
			gl=((PGraphicsOpenGL)p.g).beginGL();
			
			//just visual shit
			gl.glEnable(GL.GL_BLEND);
			gl.glBlendFunc(GL.GL_SRC_ALPHA,GL.GL_ONE);
			gl.glEnable(GL.GL_POINT_SMOOTH);
			gl.glHint(GL.GL_POINT_SMOOTH_HINT, GL.GL_NICEST);
			gl.glPointSize(size);
			
			gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
			gl.glEnableClientState(GL.GL_COLOR_ARRAY);
			
			gl.glVertexPointer(3,GL.GL_FLOAT,0,v);  
			gl.glColorPointer(4,GL.GL_FLOAT,0,c);
			
			//render sequencial range of data rom array (renders a number of slices)
			gl.glDrawArrays(GL.GL_POINTS,(numPoints/h)*slice,((h*w)/4)*depth);
			
			gl.glDisableClientState(GL.GL_COLOR_ARRAY);
			gl.glDisableClientState(GL.GL_VERTEX_ARRAY);  
			
			((PGraphicsOpenGL)p.g).endGL();
			enable = 0;
		}
	}
	
//----------------------------------------------------------------------------------------------------------------
	
	
	//Still a bit experimental - beware - seems to work in C++ but not so stable in Java.
	
	public void plotVoxrange(int enable, int start, int leng, float size){
		if(enable==1){
			
			if(updated)
			{
				//.REORDERING THE BITES.REORDERING THE BITES .CASE_INTO_FloatBuffer
				//I believe this is using physical memory AS a floatbuffer - allowing you to do java stuff like put, rewind(), etc. on OS mem 
				v = ByteBuffer.allocateDirect(4*3*numPoints).order(ByteOrder.nativeOrder()).asFloatBuffer();
				v.put(points, 0, 3*numPoints);
				v.rewind();
				
				c = ByteBuffer.allocateDirect(4*4*numPoints).order(ByteOrder.nativeOrder()).asFloatBuffer();
				c.put(cols, 0, 4*numPoints);  //putting brings us to the end of the memory
				c.rewind(); // then we rewind
				updated = false;
			}
			
			
			
			gl=((PGraphicsOpenGL)p.g).beginGL();
			
			//just visual shit
			gl.glEnable(GL.GL_BLEND);
			gl.glBlendFunc(GL.GL_SRC_ALPHA,GL.GL_ONE);
			gl.glEnable(GL.GL_POINT_SMOOTH);
			gl.glHint(GL.GL_POINT_SMOOTH_HINT, GL.GL_NICEST);
			gl.glPointSize(size);
			
			gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
			gl.glEnableClientState(GL.GL_COLOR_ARRAY);
			
			gl.glVertexPointer(3,GL.GL_FLOAT,0,v);  
			gl.glColorPointer(4,GL.GL_FLOAT,0,c);
			
			//render sequencial range of data rom array (renders a number of slices)
			gl.glDrawArrays(GL.GL_POINTS,start,leng);
			
			
			gl.glDisableClientState(GL.GL_COLOR_ARRAY);
			gl.glDisableClientState(GL.GL_VERTEX_ARRAY);  
			
			((PGraphicsOpenGL)p.g).endGL();
			enable = 0;
		}
	}
	
	//----------------------------------------------------------------------------------------------------------------	
	
        public int getArraySize() {
          return points.length;
        }

}//end of class
