import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.awt.BorderLayout;
import java.awt.event.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.universe.*;

import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.*;
import java.util.Enumeration;



public class ParticleSystem extends Object implements  Runnable
        {
        private Group group  = new Group();
        float size;
        int xdim;
        int ydim;
        int zdim;
        int count;
        int trailBoxes;
        int type;
        Particle[]  particle;
        LineParticle[]  lineParticle;

        Vector3d averagePosition, averageDirection, centeringVector;
        boolean centering = true;
        Thread operatorThread;
        ParticleWorld001 particleWorld;



        //Constructor
public ParticleSystem(int n, int s, int width, int depth, int height, int ty, int cnt, ParticleWorld001 pw) 
        {
        particleWorld = pw;
        trailBoxes = cnt;
        size = s;
        type = ty;
        count = n;    
        xdim = width;
        ydim = height;
        zdim = depth;
        centeringVector = createNormalisedVector();
        averagePosition = new Vector3d();
        averageDirection = new Vector3d();
        createParticleSystem(n);
        start();
        }

        // Generates the particleSystem
        public void createParticleSystem(int n)
                {
                // Create the branch group and transforms
                BranchGroup branchGroup = new BranchGroup();
                TransformGroup objRotation = new TransformGroup();
                TransformGroup objOffset = new TransformGroup();
                BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 250);
                objOffset.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
                objOffset.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
                
                if(type == 0)
                        {
                        particle = new Particle[n];
                        System.out.println("  SYSTEM -- BOX !!   Particle count = " +count);
                        for(int i=0;i<n;i++)
                                {
                                particle[i] = new Particle (createRandomTransform(),createNormalisedVector(), size, trailBoxes);
                                objOffset.addChild(particle[i].getChild());
                                };
                        }
                else
                        {
                        lineParticle = new LineParticle[n];
                        System.out.println("  SYSTEM -- LineParticle count = " +count);
                        for(int i=0;i<n;i++)
                                {
                                lineParticle[i] = new LineParticle (createRandomTransform(),createNormalisedVector(), size,  trailBoxes);
                                objOffset.addChild(lineParticle[i].getChild());
                                };
                        };
                // add the inner transform
                Transform3D oofst = new Transform3D();
//              oofst.setTranslation(new Vector3d((double)ParticleWorld001.sl1.getValue(),0.0, 0.0));
                objOffset.setTransform(oofst);

                // add the inner transform to the outer transform and rotate it randomly about the origin
                Transform3D oRot = new Transform3D();

                oRot.setScale(0.4);
                objRotation.setTransform(oRot);
                objRotation.addChild(objOffset);
                // Create the bounding leaf node
                BoundingLeaf boundingLeaf = new BoundingLeaf(bounds);
                objRotation.addChild(boundingLeaf);
                // add the transforms to the branch group
                branchGroup.addChild(objRotation);
                // Let Java 3D perform optimizations on this scene graph.
                branchGroup.compile();
                // add this to the output group
                group.addChild(branchGroup);
                }

public void update()
        {
        calcAveragePosition();
        calcAverageDirection();
        if(type == 1)
                {
                for(int c=0;c<count;c++)
                        {
                        lineParticle[c].setDirection(averageDirection, averagePosition);
                        lineParticle[c].update();
                        };
                }
        else
                {
                for(int c=0;c<count;c++)
                        {
                        particle[c].setDirection(averageDirection, averagePosition);
                        particle[c].update();
                        };
                };
        };

public Vector3d createRandomTransform()
        {
        double xpos = ((double)Math.random()-0.5)*2*xdim;
        double ypos = ((double)Math.random()-0.5)*2*ydim;
        double zpos = ((double)Math.random()-0.5)*2*zdim;
        Vector3d randomTransform = new Vector3d(xpos,ypos,zpos);
        return randomTransform;
        };

public Vector3d createNormalisedVector()
        {
        double xpos = ((double)Math.random()-0.5)*2*xdim;
        double ypos = ((double)Math.random()-0.5)*2*ydim;
        double zpos = ((double)Math.random()-0.5)*2*zdim;
        Vector3d randomTransform = new Vector3d(xpos,ypos,zpos);
        randomTransform.normalize();
        return randomTransform;
        };

        private void calcAveragePosition()
                {
                double tx,ty,tz;
                Vector3d tempVec = new Vector3d();
                for(int a=0;a<count;a++)
                        {
                        if(type == 0)
                                {
                                tempVec.add(particle[a].getLocation());
                                }
                        else
                                {
                                tempVec.add(lineParticle[a].getLocation());
                                };
                        };
                tx = tempVec.x / (count+1);
                ty = tempVec.y / (count+1);
                tz = tempVec.z / (count+1);
                tempVec.set(tx,ty,tz);
                averagePosition.set(tempVec);
                };

        private void calcAverageDirection()
                {
                double tx,ty,tz;
                Vector3d tempVec = new Vector3d();
                for(int a=0;a<count;a++)
                        {
                        if(type == 0)
                                {
                                tempVec.add(particle[a].getDirection());
                                }
                        else
                                {
                                tempVec.add(lineParticle[a].getDirection());
                                };
                        };
                tx = tempVec.x / (count+1);
                ty = tempVec.y / (count+1);
                tz = tempVec.z / (count+1);
                tempVec.set(tx,ty,tz);
                tempVec.normalize();

                averageDirection.set(tempVec);

                if(averagePosition.length() > 15)
                        {
                        averageDirection.scale(0.3);
                        };
                };
        // method to return the branch group to the main program when needed
   public Group getChild() 
        {
      return group;
        }
        public void start()
                {
                if (operatorThread != Thread.currentThread() )
                        {
                        operatorThread = new Thread(this);
                        operatorThread.start();
                        };
                run();
                };
        public void stop()
                {
                operatorThread = null;
                };

        public void run()
                {
                while (operatorThread == Thread.currentThread() )
                        {
                        while (particleWorld.updateOn)                          
                                {
                                try {Thread.sleep(particleWorld.sl1.getValue());} catch (Exception e) {};
                                update();
                                };
                        };
                };
}
