/*
 * Behavior class for branches of the tree
 * (used in the Tree class)
 *
 * Scott Teresi, www.teresi.us
 * March, 1999
 *
 */

import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.geometry.*;

import java.util.Random;
import java.util.Enumeration;



public class BranchBehavior extends Behavior {

  Point3d cur, prev;  // current and previous tree branch segment positions

  Tree tree;
  BranchGroup bg;
  Appearance app;
  Random rand;

  int segCount = 0;
  int maxSegs = 3;    // maximum segments before new branches MUST split off

  double spread = 0.85;
  double growth = 2.0;
  double branchingFactor = 0.2;
  double wiggle = 0.3;
  int msPerFrame = 2000;
  double branchRadius = 0.1;



  public BranchBehavior(BranchGroup b, Point3d p, Point3d c, Tree t) {

    tree = t;
    bg = b;
    cur = c;
    prev = p;

    Color3f treeColor = new Color3f(.4f, .23f, .02f);
    Material mat = new Material();
    mat.setAmbientColor(treeColor);
    mat.setSpecularColor(new Color3f(1.0f, 0.8f, 0.5f));
    mat.setDiffuseColor(new Color3f(1.0f, 0.8f, 0.5f));
    app = new Appearance();
    app.setMaterial(mat);

    tree.incrementBranchCount();
    addSegment(prev, cur, branchRadius, app);

    rand = new Random();

  }



  public void initialize() {

    this.wakeupOn(new WakeupOnElapsedTime(msPerFrame));
  }



  public void processStimulus(Enumeration enum) {

    float test = rand.nextFloat();

    /*
      Each branch is scheduled to grow a new segment every msPerFrame
      milliseconds. If it's time for this branch to split, this branch
      will be terminated (its schedule never again wakes up) and two
      new branches will be instantiated at the tip of the branch.
    */

    if ((test < branchingFactor) || (segCount > maxSegs)) {

      // split the branch

      // using rand.nextGaussian() instead of rand.nextDouble()
      double newX, newY, newZ;
      newX = cur.x + rand.nextGaussian() * spread;
      newZ = cur.z + rand.nextGaussian() * spread;
      // solved for y in: d = sqrt(x^2 + y^2 + z^2)  where d is growth
      newY = Math.pow(growth, 2d) - Math.pow(Math.abs(newX-cur.x), 2d) -
	Math.pow(Math.abs(newZ-cur.z), 2d);
      if (newY < 0) newY = 0;
        else newY = Math.sqrt(newY);
      newY += cur.y;
      Point3d newSeg1 = new Point3d(newX, newY, newZ);
      newX = cur.x + rand.nextGaussian() * spread;
      newZ = cur.z + rand.nextGaussian() * spread;
      newY = Math.pow(growth, 2d) - Math.pow(Math.abs(newX-cur.x), 2d) -
	Math.pow(Math.abs(newZ-cur.z), 2d);
      if (newY < 0) newY = 0;
        else newY = Math.sqrt(newY);
      newY += cur.y;
      Point3d newSeg2 = new Point3d(newX, newY, newZ);

      BranchGroup branch1 = new BranchGroup();
      BranchGroup branch2 = new BranchGroup();
      BranchBehavior branchBehavior1 =
	new BranchBehavior(branch1, cur, newSeg1, tree);
      branchBehavior1.setSchedulingBounds(new BoundingSphere(
					      new Point3d(0d, 0d, 0d), 10d));
      BranchBehavior branchBehavior2 =
	new BranchBehavior(branch2, cur, newSeg2, tree);
      branchBehavior2.setSchedulingBounds(new BoundingSphere(
					      new Point3d(0d, 0d, 0d), 10d));
      branch1.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
      branch2.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
      branch1.addChild(branchBehavior1);
      branch2.addChild(branchBehavior2);
      branch1.compile();
      branch2.compile();
      bg.addChild(branch1);
      bg.addChild(branch2);

      return;
    }


    // add a new branch segment

    double newX, newY, newZ;
    newX = cur.x + cur.x - prev.x + rand.nextGaussian() * wiggle;
    newY = cur.y + growth;
    newZ = cur.z + cur.z - prev.z + rand.nextGaussian() * wiggle;
    Point3d newSeg = new Point3d(newX, newY, newZ);

    prev = cur;
    cur = newSeg;

    addSegment(prev, cur, branchRadius, app);

    this.wakeupOn(new WakeupOnElapsedTime(msPerFrame));
  }


  
  public void addSegment(Point3d prev, Point3d cur, double branchRadius,
			 Appearance app) {

    tree.incrementSegmentCount();
    segCount ++;

    BranchGroup newSeg = new BranchGroup();
    newSeg.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
    newSeg.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
    SegmentBehavior segmentBehavior =
      new SegmentBehavior(newSeg, prev, cur, branchRadius, app);
    segmentBehavior.setSchedulingBounds(new BoundingSphere(
					      new Point3d(0d, 0d, 0d), 1000d));
    newSeg.addChild(segmentBehavior);
    newSeg.compile();
    bg.addChild(newSeg);

  }


}
