import processing.core.*; import osc.*; import oscP5.*; import processing.opengl.*; import java.applet.*; import java.awt.*; import java.awt.image.*; import java.awt.event.*; import java.io.*; import java.net.*; import java.text.*; import java.util.*; import java.util.zip.*; public class Metaballs extends PApplet {// ====================================================
// lib import
// ====================================================





// ====================================================
// setup()
// ====================================================
public void setup()
{
  // Applet
  size(800,600,OPENGL);  
  smooth();
  // Meta
  initMetaballs();  
  // OSC
  initOsc();
}


// ====================================================
// draw()
// ====================================================
public void draw()
{
  // Background
  background(255);
  // Metaballs
  stroke(0);
  sMetaballs.computeMesh();
  sMetaballs.draw();
}

int sNbMetaballs = 5;
Metaballs2D sMetaballs;
float       sIsoMin = 400.0f;
float       sIsoMax = 1500.0f;
boolean     sModeAttackRelease = false;



// ====================================================
// initMetaballs()
// ====================================================
public void initMetaballs()
{
  // Metaballs  
  sMetaballs = new Metaballs2D();
  sMetaballs.init(60,60);
  sMetaballs.setIsoValue( (sIsoMax+sIsoMin)/2.0f );
  sMetaballs.createMeta(sNbMetaballs);
}

// ====================================================
// sPositionMetaballs()
// ====================================================
public void sPositionMetaballs(float[] x, float[] y)
{
   for (int i=0 ; i<sNbMetaballs; i++)
   {
     //sMetaballs.getMeta(i).M_setPosition(0.0f,0.0f);
     sMetaballs.getMeta(i).M_setPosition(x[i], 1.0f-y[i]);
   }
        
}

// ====================================================
// sSetIsovalueMetaballs()
// ====================================================
public void sSetIsovalueMetaballs(float t)
{
  sMetaballs.setIsoValue( sIsoMin + t*(sIsoMax-sIsoMin) );
}

// ====================================================
// sSetIsoLiquidMetaballs()
// ====================================================
public void sSetIsoLiquidMetaballs(float v)
{
  sMetaballs.setIsoLiquid( v );
  
}

// ====================================================
// sSetModeMetaballs()
// ====================================================
public void sSetModeMetaballs(float v)
{
  sModeAttackRelease = (v == 0.0f ? false : true);
}

// ====================================================
// sSetStrengthMetaballs()
// ====================================================
public void sSetStrengthMetaballs(float[] s)
{
   for (int i=0 ; i<sNbMetaballs; i++)
     sMetaballs.getMeta(i).M_setStrenght(s[i]);
}

//	 ==================================================
//	 class Metaball
//	 ==================================================
         class Metaball
         {
             float m_strength;
             float m_x,m_y;  
         
             public void M_setStrenght(float s)
             {
               m_strength = s;
             }
         
             public void M_setPosition(float x, float y)
             {
                m_x = x;
                m_y = y;
             }
         
         }

//	 ==================================================
//	 class EdgeVertex
//	 ==================================================
	class EdgeVertex
	{
	  float x,y;
	  EdgeVertex(float x, float y){this.x=x;this.y=y;}
	};


//	 ==================================================
//	 class Metaballs2D
//	 ==================================================
	class Metaballs2D
	{
	  // Isovalue
	  // ------------------
	  float isovalue;
          float isoliquid;
	  
	  // Grid
	  // ------------------
	  int  resx,resy;
	  float stepx,stepy;
	  float[] gridValue;
	  int nbGridValue;  

	  // Voxels      
	  // ------------------
	  int[] voxel;
	  int nbVoxel;
	  
	  // EdgeVertex
	  // ------------------
	  EdgeVertex[] edgeVrt;
	  int nbEdgeVrt;  
	  
	  // Lines
	  // what we pass to the renderer
	  // ------------------
	  int[] lineToDraw;  
	  int nbLineToDraw;
	  
	  // ------------------
          // Metaballs List
	  // ------------------
          Metaball[] meta;
          int nbMeta;


	  // Constructor
	  // ------------------
	  Metaballs2D()
          {
           isoliquid = 1.0f; 
          }


          public void setIsoLiquid(float v)
          {
            isoliquid = v;  
            if (isoliquid < 0.0001f)
              isoliquid = 0.0001f;
          }

          public void createMeta(int nb)
          {
            meta = new Metaball[nb];
            nbMeta = nb;
              for (int i=0;i<nb ; i++)
              {
                 meta[i] = new Metaball();
                 meta[i].M_setPosition(0.5f, 0.5f);
                meta[i].M_setStrenght(10.0f);
              }
           }
	  
           public Metaball getMeta(int i)
           {
             if (i >= nbMeta) return null;
             return meta[i];  
           }

	  // init(int, int)
	  // ------------------
	  public void init(int resx, int resy)
	  {
	 	this.resx = resx;
		this.resy = resy;

		this.stepx = 1.0f/( (float)(resx-1));
		this.stepy = 1.0f/( (float)(resy-1));

		// Allocate gridValue
		nbGridValue	= resx*resy;
		gridValue	= new float[nbGridValue];
//                gridValuePrev   = new float[nbGridValue];

                for (int i=0; i<nbGridValue;  i++)
                  gridValue[i] = 0.0f;
		
		
		// Allocate voxels
		nbVoxel		= nbGridValue;
		voxel		= new int[nbVoxel];

		// Allocate EdgeVertices
        edgeVrt = new EdgeVertex [2*nbVoxel];
        nbEdgeVrt = 0;

		// Allocate Lines
		lineToDraw = new int[2*nbVoxel];
	        nbLineToDraw = 0;
		
	        // Precompute some values
		int x,y,n,index;
		n = 0;
		for (x=0 ; x<resx ; x++)
			for (y=0 ; y<resy ; y++)
			{
				index = 2*n;
				// index to edgeVrt
                voxel[x+resx*y] = index;
				// values
                edgeVrt[index]   = new EdgeVertex(x*stepx, y*stepy);
                edgeVrt[index+1] = new EdgeVertex(x*stepx, y*stepy);
                // gridValue Clear
                gridValue[x+resx*y] = 0;
				// Next!
				n++;
			}
		
	  }
	  
	  // computeIsovalue()
	  // ------------------
	  public void computeIsovalue()
	  {
		float	 vx,vy;
		float	 dist;
                float    contribution;
                Metaball m;

		int x,y,i,off;
		vx = 0.0f;



		for (x=0 ; x<resx; x++)
		{	
			vy = 0.0f;
			for (y=0 ; y<resy; y++)
			{
			        contribution = 0.0f;
                                for (i=0;i<nbMeta;i++)
                               {
                                   m = meta[i];
                                   dist = (float)sqrt((vx-m.m_x)*(vx-m.m_x) + (vy-m.m_y)*(vy-m.m_y));
                                   contribution += m.m_strength/(dist*dist+0.001f);
                                }
                               off = x+resx*y;
                               
				gridValue[off] += (contribution-gridValue[off]) * isoliquid;
				vy+=stepy;
			}
			vx+=stepx;
		}
	  
          }

	  
	  // computeMesh()
	  // ------------------
	  public void computeMesh()
	  {
		// Compute IsoValue
		computeIsovalue();
		// Get Lines indices

		int					x,y,squareIndex,n;
		int					iEdge;
		int					offx, offy, offAB;
		int					toCompute;
		int					offset;
		float					t;
		float					vx,vy;
		int[]                                   edgeOffsetInfo;

		nbLineToDraw = 0;
		vx	     = 0.0f;
		for (x=0 ; x<resx-1 ; x++)
		{
			vy = 0.0f;
			for (y=0 ; y<resy-1 ; y++)
			{
				offset		= x + resx*y;
				squareIndex     = getSquareIndex(x,y);

				n	        = 0;
				while ( (iEdge = MetaballsTable.edgeCut[squareIndex][n++]) != -1)
				{
					edgeOffsetInfo          = MetaballsTable.edgeOffsetInfo[iEdge];
					offx			= edgeOffsetInfo[0];
					offy			= edgeOffsetInfo[1];
					offAB			= edgeOffsetInfo[2];

	                               lineToDraw[nbLineToDraw++] = voxel[(x+offx) + resx*(y+offy)] + offAB;
				}

	                        toCompute = MetaballsTable.edgeToCompute[squareIndex];
				if (toCompute>0)
				{
	                                if ( (toCompute & 1) > 0) // Edge 0
					{
						t	= (isovalue - gridValue[offset]) / (gridValue[offset+1] - gridValue[offset]); 
						edgeVrt[voxel[offset]].x = vx*(1.0f-t) + t*(vx+stepx);
	                                }
					if ( (toCompute & 2) > 0) // Edge 3
					{
						t	= (isovalue - gridValue[offset]) / (gridValue[offset+resx] - gridValue[offset]); 
						edgeVrt[voxel[offset]+1].y = vy*(1.0f-t) + t*(vy+stepy);
					}
				
				} // toCompute
				vy += stepy;
			}	// for y

			vx += stepx;
		}	// for x

		nbLineToDraw /= 2;
	    
	  }

	  // getSquareIndex(int,int)
	  // ------------------
	  public int getSquareIndex(int x, int y)
	  {
	    int squareIndex = 0;
	        int offy  = resx*y;
	        int offy1 = resx*(y+1);
		if (gridValue[x+offy]			< isovalue) squareIndex |= 1;
		if (gridValue[x+1+offy]		        < isovalue) squareIndex |= 2;
		if (gridValue[x+1+offy1]	        < isovalue) squareIndex |= 4;
		if (gridValue[x+offy1]		        < isovalue) squareIndex |= 8;
	   return squareIndex;
	  }
	  
	  // setIsoValue(float)
	  // ------------------
	  public void setIsoValue(float newIso){this.isovalue = newIso;}
	  
	  // draw()
	  // ------------------
	  public void draw()
	  {
	        
	        int iA,iB;
	        int iLine;
		for (iLine = 0 ; iLine<nbLineToDraw ; iLine++)
		{
	                iA = lineToDraw[iLine*2];
	                iB = lineToDraw[iLine*2+1];
	                
	                line (edgeVrt[iA].x*(float)width, edgeVrt[iA].y*(float)height, edgeVrt[iB].x*(float)width, edgeVrt[iB].y*(float)height); 
		}
	  
	  }

	  // drawGrid()
	  // ------------------
	  public void drawGrid(int wx, int wy)
	  {
		int	x,y;
		float	vx,vy;
		float	sx = this.stepx*wx;
		float	sy = this.stepy*wy;

		vy = 0.0f;
		for (x=0 ; x<resx ; x++)
		{
	                line(0.0f,vy,wx,vy);
			vy += sy;
		}	

		vx = 0.0f;
		for (y=0 ; y<resy ; y++)
		{
	                line(vx,0.0f,vx,wy);
			vx += sx;
		}	
		

	  }
	};



	
	
//	 ==================================================
//	 class MetaballsTable
//	 ==================================================
	static class MetaballsTable
	{
	  // Edge Cut Array
	  // ------------------------------
	  static int edgeCut[][] =   
	  {
		{-1, -1, -1, -1, -1}, //0
		{ 0,  3, -1, -1, -1}, //3
		{ 0,  1, -1, -1, -1}, //1
		{ 3,  1, -1, -1, -1}, //2
		{ 1,  2, -1, -1, -1}, //0
		{ 1,  2,  0,  3, -1}, //3
		{ 0,  2, -1, -1, -1}, //1
		{ 3,  2, -1, -1, -1}, //2
		{ 3,  2, -1, -1, -1}, //2
		{ 0,  2, -1, -1, -1}, //1
		{ 1,  2,  0,  3, -1}, //3
		{ 1,  2, -1, -1, -1}, //0
		{ 3,  1, -1, -1, -1}, //2
		{ 0,  1, -1, -1, -1}, //1
		{ 0,  3, -1, -1, -1}, //3
		{-1, -1, -1, -1, -1}  //0
	  };

	  // EdgeOffsetInfo Array
	  // ------------------------------
	  static int edgeOffsetInfo[][] =  
	  {
	 	{0,0,0},
		{1,0,1},
		{0,1,0},
		{0,0,1}
	 
	  };

	  // EdgeToCompute Array
	  // ------------------------------
	  static int edgeToCompute[] = {0,3,1,2,0,3,1,2,2,1,3,0,2,1,3,0};


	}



OscP5 oscP5;
String pattern_meta           = "/Metaballs";
String pattern_meta_x         = pattern_meta + "/x"; 
String pattern_meta_y         = pattern_meta + "/y"; 
String pattern_meta_attack    = pattern_meta + "/attack"; 
String pattern_meta_release   = pattern_meta + "/release"; 
String pattern_meta_iso       = "/isoValue/x"; 
String pattern_meta_isoliquid = "/isoLiquid/x";
String pattern_meta_mode      = "/mode/x";


float[] osc_x;
float[] osc_y;


// ====================================================
// initOSC
// ====================================================
boolean sIsInitOSC = false;
public void initOsc() 
{
  // OSC objects
  int receiveAtPort      = 9000;
  int sendToPort         = 9000;
  String host            = "192.168.2.7";
  String oscP5event      = "oscEvent";
  oscP5                  = new OscP5(this, host, sendToPort, receiveAtPort, oscP5event);

  //Arrays to store values
  osc_x = new float[sNbMetaballs];
  osc_y = new float[sNbMetaballs];

  sIsInitOSC = true;
}

// ====================================================
// oscEvent()
// ====================================================
public void oscEvent(OscIn oscIn) 
{
  if (!sIsInitOSC)
    return;

  Object[] o = oscIn.getData();

  if (o!=null)
  {  
    // X
    if(oscIn.checkAddrPattern(pattern_meta_x))
      if(oscIn.checkTypetag("fffff")) 
        for (int i=0;i<sNbMetaballs;i++)
          osc_x[i] = oscIn.getFloat(i);  

    // Y
    if(oscIn.checkAddrPattern(pattern_meta_y)) 
      if(oscIn.checkTypetag("fffff"))  // 5 meta
        for (int i=0;i<sNbMetaballs;i++)
          osc_y[i] = oscIn.getFloat(i);  

    sPositionMetaballs(osc_x, osc_y);  

    // ISOVALUE
    if(oscIn.checkAddrPattern(pattern_meta_iso)) 
      if(oscIn.checkTypetag("f"))
        sSetIsovalueMetaballs(oscIn.getFloat(0));
    //    sSetIsovalueMetaballs( oscGetFloat(oscIn,pattern_meta_iso, 100.0f) ); 
    // ISOLIQUID
    //sSetIsoLiquidMetaballs( oscGetFloat(oscIn,pattern_meta_isoliquid, 1.0f) );
    if(oscIn.checkAddrPattern(pattern_meta_isoliquid)) 
      if(oscIn.checkTypetag("f"))
        sSetIsoLiquidMetaballs(oscIn.getFloat(0));


    // MODE
    //sSetModeMetaballs( oscGetFloat(oscIn,pattern_meta_mode, 0.0f) );        
    if(oscIn.checkAddrPattern(pattern_meta_isoliquid)) 
      if(oscIn.checkTypetag("f"))
        sSetModeMetaballs( oscIn.getFloat(0) );        

/*
    if (sModeAttackRelease)
    {
      if(oscIn.checkAddrPattern(pattern_meta_attack) || oscIn.checkAddrPattern(pattern_meta_release) ) 
        if(oscIn.checkTypetag("f"))
          sSetStrengthMetaballs( 10.0f * oscIn.getFloat(0) );        
    }
*/
  }
}


// ====================================================
// oscGetFloat()
// ====================================================
public float oscGetFloat(OscIn oscIn, String pattern, float def)
{
  print(oscIn.checkAddrPattern(pattern));
  if(oscIn.checkAddrPattern(pattern)) 
  {
    if(oscIn.checkTypetag("f"))
      return oscIn.getFloat(0);  
  }
  return def;
}
static public void main(String args[]) {   PApplet.main(new String[] { "Metaballs" });}}