/**
 *  Copyright (c) 2005-2007 by Hank Dolben
 *  Licensed under the Open Software License version 2.1
 *  http://opensource.org/licenses/osl-2.1.php
 */
package org.dolben.anim;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.dolben.iiid.*;
import org.dolben.poly.Cuboid;
import org.dolben.poly.Polyhedron;
import org.dolben.poly.ShadedFaces;
import org.dolben.poly.Solid;

/**
 *  An animated simulation of solids bouncing around in a box.
 *  Override createBodies() in a concrete class.
 */
public abstract class Collidoscope extends AnimationApplet {
    
    private Projector projector;    // the projector to draw 3D
    private List bodies;            // the list of moving objects
    private Solid trap;             // the box they're in
    
    /**
     *  Creates the bodies in the simulation.
     *
     *  @param limit the maxima in 3D of the box
     */
    protected abstract void createBodies( double[] limit );
    
    /**
     *  Creates the moving 3D objects, a cuboid that contains them,
     *  and a projector to draw them in 2D.
     */
    protected void initAnimation( ) {
        period = 50;
        double width = getSize().width-3;
        double height = getSize().height-3;
        double depth = ( width < height ) ? width : height;
        projector = new Projector(width+2,height+2,depth/2,2.5*depth);
        Cuboid cuboid = new Cuboid(width,height,depth);
        cuboid.turnInsideOut();
        trap = new ShadedFaces(cuboid,Color.gray);
        bodies = new LinkedList();
        double[] limit = cuboid.getExtent();
        createBodies(limit);
        double vmax =
			(period/1000.0)*Rn.magnitude(limit)/Math.sqrt(2*bodies.size());
        Iterator it = bodies.iterator();
        while ( it.hasNext() ) {
            Body body = (Body)it.next();
            body.setVelocity(vrandge(vmax),limit);
        }
    }
    
    /**
     *  Draws all of the objects, then moves them and handles collisions
     *  between them.
     *
     *  @param graphics the drawing context
     */
    public void paintFrame( Graphics graphics ) {
        Graphics2D g2d = (Graphics2D)graphics;
        g2d.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON
        );
        g2d.setStroke(new BasicStroke(0.75f));
        trap.paint(projector,graphics);
        Collections.sort(bodies);
        Iterator it = bodies.iterator();
        while ( it.hasNext() ) {
            Body body = (Body)it.next();
            body.paint(projector,graphics);
        }
        it = bodies.iterator();
        while ( it.hasNext() ) {
            Body body = (Body)it.next();
            body.step();
        }
        for ( int i = 0; i < bodies.size()-1; ++i ) {
            Body body = (Body)bodies.get(i);
            for ( int j = i+1; j < bodies.size(); ++j ) {
                body.collide((Body)bodies.get(j));
            }
        }
    }
    
    /**
     *  Creates a Body, adding it the list, for the given polyhedron,
     *  normalizes the radius and randomizes position, velocity and spin
     */
    protected void initBody( Solid solid, double r ) {
        Cuboid cuboid = (Cuboid)trap.getPolyhedron();
        double[] limit = cuboid.getExtent();
        Polyhedron polyhedron = solid.getPolyhedron();
        polyhedron.setRadius(r);
        Body body = new Body(solid);
        double[] offset = new double[3];
        for ( int i = 0; i < 3; i++ ) {
            offset[i] = randge(limit[i]-r);
        }
        polyhedron.translate(offset);
        double scale = period/100.0;
        body.setRotation(
            rotationD(randge(Math.PI),randge(Math.PI/2),randge(scale*Math.PI/15))
        );
        bodies.add(body);
    }
    
    /**
     *  Generates a 3D matrix for a rotation by an angle around the direction
     *  given by spherical coordinate angles (theta,phi).
     *
     *  @param phi the angle from the x axis, around the z axis
     *  @param theta the angle from the z axis, around the y axis
     *  @param rho the angle around the x axis
     *
     *  @return the rotation matrix
     */
    private double[][] rotationD( double phi, double theta, double rho ) {
        // put x in the given direction
        double[][] d = Rn.multiply(R3.rotationY(-theta),R3.rotationZ(phi));
        return Rn.multiply(Rn.transpose(d),Rn.multiply(R3.rotationX(rho),d));
    }
    
    /*
     *  Generates a pseudo-random vector where each component is
     *  in the range -r to r.
     */
    private double[] vrandge( double r ) {
        double[] v = new double[3];
        for ( int i = 0; i < 3; ++i ) {
            v[i] = randge(r);
        }
        return v;
    }
    
    // Generates a pseudo-random number in the range -r to r.
    private double randge( double r ) {
        return 2*rand(r)-r;
    }
    
    // Generates a pseudo-random number in the range 0 to r.
    private double rand( double r ) {
        return r*Math.random();
    }

}
