import processing.core.*; 
import processing.data.*; 
import processing.event.*; 
import processing.opengl.*; 

import peasy.*; 

import java.util.HashMap; 
import java.util.ArrayList; 
import java.io.File; 
import java.io.BufferedReader; 
import java.io.PrintWriter; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.io.IOException; 

public class HangingNet extends PApplet {



int NUM_NODES = 11;
float SlackFactor = 1.0f;
PVector Gravity = new PVector(0, 0, -0.98f);
float DampingFactor = 0.9f;
PeasyCam cam;

class Node
{
    PVector Coord, Force, Velocity;
    boolean XFixed, YFixed, ZFixed;

    Node (float x, float y, float z, boolean xfix, boolean yfix, boolean zfix)
    {
        Coord = new PVector(x, y, z);
        Force = new PVector();
        Velocity = new PVector();
        XFixed = xfix;
        YFixed = yfix;
        ZFixed = zfix;
    }
    
    public void Fix()
    {
        XFixed = YFixed = ZFixed = true;
    }
}

class Line
{
    Node NodeStart, NodeEnd;
    float SlackLength;

    Line (Node start, Node end)
    {
        NodeStart = start;
        NodeEnd = end;
        SlackLength = GetLength();
        SlackLength *= SlackFactor;
    }
    
    public float GetLength()
    {
        return PVector.sub(NodeEnd.Coord,NodeStart.Coord).mag();
    }
}

Node[] arrayNodes;
Line[] arrayLines;

public void setup() 
{ 
    size(800, 800, P3D);
    cam = new PeasyCam(this, width/2, height/2, -200, 1000);
    cam.setRotations(-PI/2, 0, 0);
    
    float border = 1.0f/4.0f;
    
    arrayNodes = new Node[NUM_NODES*NUM_NODES];
    for (int xcount=0; xcount<NUM_NODES; xcount++)
    {
        for (int ycount=0; ycount<NUM_NODES; ycount++)
        {
            arrayNodes[xcount*NUM_NODES+ycount] = new Node(width*border+(1-2*border)*xcount*width/(NUM_NODES-1), height*border+(1-2*border)*ycount*height/(NUM_NODES-1), 0.0f, false, false, false);
        }
    }
    arrayNodes[0].Fix();
    arrayNodes[NUM_NODES-1].Fix();
    arrayNodes[(NUM_NODES-1)*NUM_NODES].Fix();
    arrayNodes[NUM_NODES*NUM_NODES-1].Fix();
    
    Node startNode, endNode;
    int count = 0;
    arrayLines = new Line[2*NUM_NODES*(NUM_NODES-1)];
    for (int xcount=0; xcount<NUM_NODES; xcount++)
    {
        for (int ycount=0; ycount<NUM_NODES; ycount++)
        {
            if (xcount<NUM_NODES-1)
            {
                startNode = arrayNodes[xcount*NUM_NODES+ycount];
                endNode = arrayNodes[(xcount+1)*NUM_NODES+ycount];
                arrayLines[count++] = new Line(startNode, endNode);
            }
            if (ycount<NUM_NODES-1)
            {
                startNode = arrayNodes[xcount*NUM_NODES+ycount];
                endNode = arrayNodes[xcount*NUM_NODES+ycount+1];
                arrayLines[count++] = new Line(startNode, endNode);
            }
        }
    }
    
} 

public void draw() 
{ 
    background(0.0f, 0.0f, 0.0f);
    //lights();
    Node aNode;
    float move_step = 10.0f;
    
    if ((keyPressed)&&(key==CODED)) 
    {
        switch (keyCode)
        {
        case UP:
            aNode = arrayNodes[NUM_NODES-1];
            aNode.Coord.z += move_step;
            break;
        
        case DOWN:
            aNode = arrayNodes[NUM_NODES-1];
            aNode.Coord.z -= move_step;
            break;
        
        case LEFT:
            aNode = arrayNodes[NUM_NODES-1];
            aNode.Coord.x -= move_step;
            break;
        
        case RIGHT:
            aNode = arrayNodes[NUM_NODES-1];
            aNode.Coord.x += move_step;
            break;
        
        case CONTROL:
            if (mousePressed)
            {
                if (mouseButton==LEFT)
                {
                    aNode = arrayNodes[0];
                }
                else 
                {
                    aNode = arrayNodes[NUM_NODES-1];
                }
                aNode.Coord.x = mouseX;
                aNode.Coord.z = (height/2-mouseY);
            }
            break;
        }
    }
    
    UpdateCoords(1.0f);

    DrawLines();
    DrawNodes();
} 

public void keyPressed() {
  if (key == 32) setup();
}

public void UpdateCoords(float time_step)
{
    Node aNode;
    Line aLine;
    float force, linelength;
    PVector dircos, gravity = PVector.mult(Gravity, time_step);
    
    for (int count=0; count<arrayLines.length; count++)
    {
        aLine = arrayLines[count];
        linelength = aLine.GetLength();
        force = 10.0f*(linelength-aLine.SlackLength)/aLine.SlackLength;
        dircos = PVector.sub(aLine.NodeEnd.Coord, aLine.NodeStart.Coord);
        dircos.setMag(0.5f * force);
        
        aLine.NodeStart.Force.add(dircos);
        aLine.NodeEnd.Force.sub(dircos);
    }

    for (int count=0; count<arrayNodes.length; count++)
    {
        aNode = arrayNodes[count];
        aNode.Velocity.add(PVector.mult(aNode.Force, time_step));
        aNode.Velocity.add(gravity);  //  Gravity
        if (aNode.XFixed) aNode.Velocity.x = 0.0f;
        if (aNode.YFixed) aNode.Velocity.y = 0.0f;
        if (aNode.ZFixed) aNode.Velocity.z = 0.0f;
        aNode.Coord.add(PVector.mult(aNode.Velocity, time_step));
        aNode.Force = new PVector();      //  ready for next time
        aNode.Velocity.mult(DampingFactor);  //  ready for next time
    }

}

public void DrawNodes()
{
    Node aNode;

    strokeWeight(10);
    //smooth();
    beginShape(POINTS);
    for (int count=0; count<arrayNodes.length; count++)
    {
        aNode = arrayNodes[count];
        stroke(128+128*PApplet.parseInt(aNode.XFixed), 128+128*PApplet.parseInt(aNode.YFixed), 128+128*PApplet.parseInt(aNode.ZFixed));  //  White
        vertex(aNode.Coord.x, aNode.Coord.y, aNode.Coord.z);
    }
    endShape();
    //noSmooth();
}

public void DrawLines()
{
    Line aLine;

    stroke(255, 255, 255);  //  White
    strokeWeight(2);
    beginShape(LINES);
    for (int count=0; count<arrayLines.length; count++)
    {
        aLine = arrayLines[count];
        vertex(aLine.NodeStart.Coord.x, aLine.NodeStart.Coord.y, aLine.NodeStart.Coord.z);
        vertex(aLine.NodeEnd.Coord.x, aLine.NodeEnd.Coord.y, aLine.NodeEnd.Coord.z);
    }
    endShape();
}


    static public void main(String[] passedArgs) {
        String[] appletArgs = new String[] { "HangingNet" };
        if (passedArgs != null) {
          PApplet.main(concat(appletArgs, passedArgs));
        } else {
          PApplet.main(appletArgs);
        }
    }
}
