/*
 * Behavior class for segments of the tree
 * (used by the BranchBehavior class)
 *
 * Scott Teresi, www.teresi.us
 * March, 1999
 *
 */

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



public class SegmentBehavior extends Behavior {

  Point3d cur, prev;  // current and previous tree branch segment positions
                      // (effectively, the two ends of this branch segment)
  Appearance app;
  double branchRadius;
  BranchGroup bg;

  double growthRate = .016;
  int msPerFrame = 200;



  public SegmentBehavior(BranchGroup b, Point3d p, Point3d c,
			 double r, Appearance a) {
    bg = b;
    cur = c;
    prev = p;
    branchRadius = r;
    app = a;

    growSegment();
  }



  public void initialize() {

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



  public void processStimulus(Enumeration enum) {

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


  
  void growSegment() {

    branchRadius += growthRate;
    BranchGroup newSeg = createCylinder(prev, cur, branchRadius, app);
    newSeg.setCapability(newSeg.ALLOW_DETACH);
    newSeg.compile();

    if (bg.numChildren() > 0)
      bg.setChild(newSeg, 0);
    else
      bg.addChild(newSeg);
  }



  // return a BranchGroup containing a cylinder extending from points b to a
  // (ugly method)

  BranchGroup createCylinder(Point3d b, Point3d a, double radius,
			     Appearance cylApp) {


    Vector3f base = new Vector3f();
    base.x = (float) b.x;
    base.y = (float) b.y;
    base.z = (float) b.z;
    Vector3f apex = new Vector3f();
    apex.x = (float) a.x;
    apex.y = (float) a.y;
    apex.z = (float) a.z;

    // calculate center of object
    Vector3f center = new Vector3f();
    center.x = (apex.x - base.x) / 2 + base.x;
    center.y = (apex.y - base.y) / 2 + base.y;
    center.z = (apex.z - base.z) / 2 + base.z;

    // calculate height of object and unit vector along cylinder axis
    Vector3f unit = new Vector3f();
    unit.sub(apex, base);  // unit = apex - base;
    float height = unit.length();
    unit.normalize();

    /* A Java3D cylinder is created lying on the Y axis by default.
       The idea here is to take the desired cylinder's orientation
       and perform a tranformation on it to get it ONTO the Y axis.
       Then this transformation matrix is inverted and used on a
       newly-instantiated Java 3D cylinder. */

    // calculate vectors for rotation matrix
    // rotate object in any orientation, onto Y axis (exception handled below)
    // (see page 418 of Computer Graphics by Hearn and Baker)
    Vector3f uX = new Vector3f();
    Vector3f uY = new Vector3f();
    Vector3f uZ = new Vector3f();
    float magX;
    Transform3D rotateFix = new Transform3D();

    uY = new Vector3f(unit);
    uX.cross(unit, new Vector3f(0, 0, 1));
    magX = uX.length();
    // magX == 0 if object's axis is parallel to Z axis
    if (magX != 0) {
      uX.z = uX.z / magX;
      uX.x = uX.x / magX;
      uX.y = uX.y / magX;
      uZ.cross(uX, uY);
    }
    else {
      // formula doesn't work if object's axis is parallel to Z axis
      // so rotate object onto X axis first, then back to Y at end
      float magZ;
      // (switched z -> y,  y -> x, x -> z from code above)
      uX = new Vector3f(unit);
      uZ.cross(unit, new Vector3f(0, 1, 0));
      magZ = uZ.length();
      uZ.x = uZ.x / magZ;
      uZ.y = uZ.y / magZ;
      uZ.z = uZ.z / magZ;
      uY.cross(uZ, uX);
      // rotate object 90 degrees CCW around Z axis--from X onto Y
      rotateFix.rotZ(Math.PI / 2.0);
    }

    // create the rotation matrix
    Transform3D transMatrix = new Transform3D();
    Transform3D rotateMatrix =
	new Transform3D(new Matrix4f(uX.x, uX.y, uX.z, 0,
				     uY.x, uY.y, uY.z, 0,
				     uZ.x, uZ.y, uZ.z, 0,
				     0,  0,  0,  1));
    // invert the matrix; need to rotate it off of the Z axis
    rotateMatrix.invert();
    // rotate the cylinder into correct orientation
    transMatrix.mul(rotateMatrix);
    transMatrix.mul(rotateFix);
    // translate the cylinder away
    transMatrix.setTranslation(center);
    // create the transform group
    TransformGroup tg = new TransformGroup(transMatrix);

    Cylinder cyl = new Cylinder((float) radius, height,
				Cylinder.GENERATE_NORMALS, 7, 1, cylApp);
    tg.addChild(cyl);
    BranchGroup cylBg = new BranchGroup();
    cylBg.addChild(tg);
    return cylBg;
  }


}

