Viewer : Appearance « 3D « Java






Viewer

Viewer

/*
 * %Z%%M% %I% %E% %U%
 * 
 * ************************************************************** "Copyright (c)
 * 2001 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * -Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * 
 * -Redistribution in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.
 * 
 * You acknowledge that Software is not designed,licensed or intended for use in
 * the design, construction, operation or maintenance of any nuclear facility."
 * 
 * ***************************************************************************
 */

import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.text.NumberFormat;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.EventObject;
import java.util.Hashtable;
import java.util.Vector;

import javax.media.j3d.Alpha;
import javax.media.j3d.Appearance;
import javax.media.j3d.Background;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.ImageComponent;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.LineArray;
import javax.media.j3d.LineAttributes;
import javax.media.j3d.LineStripArray;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.RotationInterpolator;
import javax.media.j3d.Screen3D;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TriangleFanArray;
import javax.media.j3d.View;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.vecmath.AxisAngle4f;
import javax.vecmath.Color3f;
import javax.vecmath.Matrix4d;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;
import javax.vecmath.Vector4d;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.ViewingPlatform;

public class ViewProj extends Applet implements Java3DExplorerConstants {

  PolygonAttributes solidPa;

  PolygonAttributes wirePa;

  JSlider dynamicOffsetSlider;

  JSlider staticOffsetSlider;

  JLabel dynamicSliderValueLabel;

  JLabel staticSliderValueLabel;

  float dynamicOffset = 1.0f;

  float staticOffset = 1.0f;

  float frontClipDist = 1.413f;

  float backClipDist = 3.309f;

  float backClipRatio = backClipDist / frontClipDist;

  View view;

  ViewingPlatform viewingPlatform;

  float innerScale = 0.94f;

  TransformGroup innerTG;

  Transform3D scale;

  Transform3D projTrans = new Transform3D();

  int numClipGridPts;

  int maxClipGridPts = 180;

  Point3f[] clipGridPtsVW = new Point3f[maxClipGridPts];

  Point3f[] clipGridPtsProj = new Point3f[maxClipGridPts];

  int numCirclePts = 36;

  Point3f[] circlePtsVW = new Point3f[numCirclePts];

  Point3f[] circlePtsProj = new Point3f[numCirclePts];

  Point3f eyePtVW = new Point3f();

  float fov;

  float sphereRadius = 0.85f;

  BranchGroup urScene;

  BranchGroup lrScene;

  SimpleUniverse urUniverse;

  SimpleUniverse lrUniverse;

  boolean isApplication;

  Canvas3D canvas;

  Canvas3D urCanvas;

  Canvas3D lrCanvas;

  OffScreenCanvas3D offScreenCanvas;

  OffScreenCanvas3D urOffScreenCanvas;

  OffScreenCanvas3D lrOffScreenCanvas;

  String snapImageString = "Snap Main";

  String urSnapImageString = "Snap UR";

  String lrSnapImageString = "Snap LR";

  String outFileBase = "vproj";

  int outFileSeq = 0;

  float offScreenScale = 1.0f;

  String urOutFileBase = "vprojur";

  int urOutFileSeq = 0;

  float urOffScreenScale = 1.0f;

  String lrOutFileBase = "vprojlr";

  int lrOutFileSeq = 0;

  float lrOffScreenScale = 1.0f;

  NumberFormat nf;

  Vector4d projPt = new Vector4d();

  public BranchGroup createSceneGraph() {
    // Create the root of the branch graph
    BranchGroup objRoot = new BranchGroup();

    // Create the transform group node and initialize it to the
    // identity. Enable the TRANSFORM_WRITE capability so that
    // our behavior code can modify it at runtime. Add it to the
    // root of the subgraph.
    TransformGroup objTrans = new TransformGroup();
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    objRoot.addChild(objTrans);

    // Create a Sphere. We will display this as both wireframe and
    // solid to make a hidden line display
    // wireframe
    Appearance wireApp = new Appearance();
    ColoringAttributes ca = new ColoringAttributes(black,
        ColoringAttributes.SHADE_FLAT);
    wireApp.setColoringAttributes(ca);
    wirePa = new PolygonAttributes(PolygonAttributes.POLYGON_LINE,
        PolygonAttributes.CULL_BACK, 0.0f);
    wireApp.setPolygonAttributes(wirePa);
    Sphere outWireSphere = new Sphere(sphereRadius, 0, 10, wireApp);
    objTrans.addChild(outWireSphere);

    // solid
    ColoringAttributes outCa = new ColoringAttributes(red,
        ColoringAttributes.SHADE_FLAT);
    Appearance outSolid = new Appearance();
    outSolid.setColoringAttributes(outCa);
    solidPa = new PolygonAttributes(PolygonAttributes.POLYGON_FILL,
        PolygonAttributes.CULL_BACK, 0.0f);
    solidPa.setPolygonOffsetFactor(dynamicOffset);
    solidPa.setPolygonOffset(staticOffset);
    solidPa.setCapability(PolygonAttributes.ALLOW_OFFSET_WRITE);
    outSolid.setPolygonAttributes(solidPa);
    Sphere outSolidSphere = new Sphere(sphereRadius, 0, 10, outSolid);
    objTrans.addChild(outSolidSphere);

    innerTG = new TransformGroup();
    innerTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    scale = new Transform3D();
    updateInnerScale();
    objTrans.addChild(innerTG);

    // Create a smaller sphere to go inside. This sphere has a different
    // tesselation and color
    Sphere inWireSphere = new Sphere(sphereRadius, 0, 15, wireApp);
    innerTG.addChild(inWireSphere);

    // inside solid
    ColoringAttributes inCa = new ColoringAttributes(blue,
        ColoringAttributes.SHADE_FLAT);
    Appearance inSolid = new Appearance();
    inSolid.setColoringAttributes(inCa);
    inSolid.setPolygonAttributes(solidPa);
    Sphere inSolidSphere = new Sphere(sphereRadius, 0, 15, inSolid);
    innerTG.addChild(inSolidSphere);

    // Create a new Behavior object that will perform the desired
    // operation on the specified transform object and add it into
    // the scene graph.
    AxisAngle4f axisAngle = new AxisAngle4f(0.0f, 0.0f, 1.0f,
        -(float) Math.PI / 2.0f);
    Transform3D yAxis = new Transform3D();
    Alpha rotationAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE, 0, 0,
        80000, 0, 0, 0, 0, 0);

    RotationInterpolator rotator = new RotationInterpolator(rotationAlpha,
        objTrans, yAxis, 0.0f, (float) Math.PI * 2.0f);
    BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
        100.0);
    rotator.setSchedulingBounds(bounds);
    //objTrans.addChild(rotator);

    Background bgWhite = new Background(white);
    bgWhite.setApplicationBounds(bounds);
    objTrans.addChild(bgWhite);

    // Have Java 3D perform optimizations on this scene graph.
    objRoot.compile();

    return objRoot;
  }

  void updateInnerScale() {
    scale.set(innerScale);
    innerTG.setTransform(scale);
  }

  public BranchGroup createVWorldViewSG() {
    // Create the root of the branch graph
    BranchGroup objRoot = new BranchGroup();
    objRoot.setCapability(BranchGroup.ALLOW_DETACH);

    // setup a transform group to hold the scaled scene
    TransformGroup objTrans = new TransformGroup();
    objRoot.addChild(objTrans);

    // get the eye point, field of view and clip distances
    float fov = (float) view.getFieldOfView();

    // figure out the angle factors to find points along the edges
    // of the FOV
    // X = fovSpreadX * (Y - eyeVW.y) + eyeVW.x;
    float fovSpreadX = (float) Math.tan(fov / 2);
    // Z = fovSpreadZ * (X - eyeVW.x) + eyeVW.z;
    float fovSpreadZ = 1.0f / fovSpreadX;
    //System.out.println("fovSpreadX = " + fovSpreadX);
    //System.out.println("fovSpreadZ = " + fovSpreadZ);

    Transform3D vpTransform = new Transform3D();
    viewingPlatform.getViewPlatformTransform().getTransform(vpTransform);
    Vector3f vpTranslation = new Vector3f();
    vpTransform.get(vpTranslation);
    eyePtVW.set(vpTranslation);
    eyePtVW.negate();
    // get the eye point in our 2D coord system.
    Point3f eyePt = new Point3f(0.0f, eyePtVW.z, 0.1f);
    float frontClipDist = (float) view.getFrontClipDistance();
    float backClipDist = (float) view.getBackClipDistance();

    // set up the clip plane lines
    Point3f[] cpPoints = new Point3f[5];
    cpPoints[0] = new Point3f(frontClipDist * fovSpreadX, eyePtVW.z
        + frontClipDist, 0.1f);
    cpPoints[1] = new Point3f(cpPoints[0]);
    cpPoints[1].x *= -1;
    Point3f backLeft = new Point3f(-backClipDist * fovSpreadX, eyePtVW.z
        + backClipDist, 0.1f);
    cpPoints[2] = backLeft;
    Point3f backRight = new Point3f(backLeft);
    backRight.x *= -1;
    cpPoints[3] = backRight;
    cpPoints[4] = cpPoints[0];
    //for (int i = 0; i < 4; i++) {
    //    System.out.println("cpPoints[" + i + "] = " + cpPoints[i]);
    //}
    int[] cpLength = new int[1];
    cpLength[0] = 5;
    LineStripArray cpLines = new LineStripArray(5, LineArray.COORDINATES,
        cpLength);
    cpLines.setCoordinates(0, cpPoints);
    Appearance cpApp = new Appearance();
    ColoringAttributes cpCa = new ColoringAttributes(blue,
        ColoringAttributes.SHADE_FLAT);
    cpApp.setColoringAttributes(cpCa);
    Shape3D cpShape = new Shape3D(cpLines, cpApp);
    objTrans.addChild(cpShape);

    // get the limits of the space
    float minY = eyePt.y;
    float maxY = backLeft.y;
    float minX = backLeft.x;
    float maxX = backRight.x;

    // figure out the X and Y extents and offsets
    float deltaX = maxX - minX;
    float deltaY = maxY - minY;
    float offsetX = -(maxX + minX) / 2.0f;
    float offsetY = -(maxY + minY) / 2.0f;
    float gridSize = Math.max(deltaX, deltaY);

    // scale the grid slightly to give a border around the edge
    gridSize *= 1.1f;

    //System.out.println("offsetX = " + offsetX);
    //System.out.println("offsetY = " + offsetY);

    // Scale the view to fit -1 to 1
    Transform3D trans = new Transform3D();
    trans.set(new Vector3f(offsetX, offsetY, 0.0f), 2.0f / gridSize);
    objTrans.setTransform(trans);

    // figure out a grid step that is a multiple of 10 which keeps the
    // number of steps less than 30.
    float gridStep = 1.0f;
    while ((gridSize / gridStep) > 30.0) {
      gridStep *= 10;
    }
    int gridNumSteps = (int) Math.ceil(gridSize / gridStep) + 1;

    // allocate the grid points array, four points for each step (x and y)
    // with a couple extra points for the extra grid points added
    // below
    int gridNumPoints = 4 * (gridNumSteps + 4);
    Point3f[] gridPts = new Point3f[gridNumPoints];
    for (int i = 0; i < gridNumPoints; i++) {
      gridPts[i] = new Point3f();
    }

    // find the grid limits. Add a step on each side to make sure
    // the grid is larger than the view
    float gridMinY = gridStepFloor(minY, gridStep) - gridStep;
    float gridMaxY = gridStepCeil(maxY, gridStep) + gridStep;
    float gridMinX = gridStepFloor(minX, gridStep) - gridStep;
    float gridMaxX = gridStepCeil(maxX, gridStep) + gridStep;
    //System.out.println("gridMinY = " + gridMinY);
    //System.out.println("gridMaxY = " + gridMaxY);
    //System.out.println("gridMinX = " + gridMinX);
    //System.out.println("gridMaxX = " + gridMaxX);

    // set up the background grid
    Appearance bgApp = new Appearance();
    ColoringAttributes bgCa = new ColoringAttributes();
    bgCa.setColor(grey);
    LineAttributes bgLa = new LineAttributes();
    bgApp.setColoringAttributes(bgCa);

    // clear out the clip grid point list
    numClipGridPts = 0;

    // set up the vertical lines
    int numPts = 0;
    for (float x = gridMinX; x <= gridMaxX; x += gridStep) {
      gridPts[numPts].x = x;
      gridPts[numPts].y = gridMinY;
      gridPts[numPts].z = -0.2f;
      gridPts[numPts + 1].x = x;
      gridPts[numPts + 1].y = gridMaxY;
      gridPts[numPts + 1].z = -0.2f;
      numPts += 2;

      // try to add a line to the clipped grid
      // find the intersection of the clipped line with the FOV sides
      // this is a distance relative to the eye
      float clipZ = fovSpreadZ * Math.abs(x - eyePtVW.x);
      if (clipZ < frontClipDist) { // clip to front clip plane
        clipZ = frontClipDist;
      }
      if (clipZ < backClipDist) { // clip to back clip plane
        // line is not clipped
        clipGridPtsVW[numClipGridPts].x = x;
        clipGridPtsVW[numClipGridPts].y = clipZ + eyePtVW.z;
        clipGridPtsVW[numClipGridPts].z = -0.1f;
        clipGridPtsVW[numClipGridPts + 1].x = x;
        clipGridPtsVW[numClipGridPts + 1].y = backClipDist + eyePtVW.z;
        clipGridPtsVW[numClipGridPts + 1].z = -0.1f;
        numClipGridPts += 2;
      }
    }
    LineArray vertLa = new LineArray(numPts, LineArray.COORDINATES);
    vertLa.setCoordinates(0, gridPts, 0, numPts);
    Shape3D vertShape = new Shape3D(vertLa, bgApp);
    objTrans.addChild(vertShape);

    // set up the horizontal lines
    numPts = 0;
    for (float y = gridMinY; y <= gridMaxY; y += gridStep) {
      gridPts[numPts].x = gridMinX;
      gridPts[numPts].y = y;
      gridPts[numPts++].z = -0.2f;
      gridPts[numPts].x = gridMaxX;
      gridPts[numPts].y = y;
      gridPts[numPts++].z = -0.2f;

      // try to add a line to the clipped grid
      // find the intersection of the clipped line with the FOV sides
      // this is a distance relative to the eye
      float clipDist = (y - eyePtVW.z);
      if ((clipDist > frontClipDist) && (clipDist < backClipDist)) {

        float clipX = fovSpreadX * clipDist;
        clipGridPtsVW[numClipGridPts].x = -clipX;
        clipGridPtsVW[numClipGridPts].y = y;
        clipGridPtsVW[numClipGridPts].z = -0.1f;
        clipGridPtsVW[numClipGridPts + 1].x = clipX;
        clipGridPtsVW[numClipGridPts + 1].y = y;
        clipGridPtsVW[numClipGridPts + 1].z = -0.1f;
        numClipGridPts += 2;
      }
    }
    LineArray horizLa = new LineArray(numPts, LineArray.COORDINATES);
    horizLa.setCoordinates(0, gridPts, 0, numPts);
    Shape3D horizShape = new Shape3D(horizLa, bgApp);
    objTrans.addChild(horizShape);

    // draw the clipped grid.
    if (numClipGridPts > 0) {
      LineArray clipLa = new LineArray(numClipGridPts,
          LineArray.COORDINATES);
      clipLa.setCoordinates(0, clipGridPtsVW, 0, numClipGridPts);
      Appearance clipGridApp = new Appearance();
      ColoringAttributes clipCa = new ColoringAttributes(black,
          ColoringAttributes.SHADE_FLAT);
      clipGridApp.setColoringAttributes(clipCa);
      LineAttributes clipGridLa = new LineAttributes();
      Shape3D clipShape = new Shape3D(clipLa, clipGridApp);
      objTrans.addChild(clipShape);
    }

    // set up the coordinate system
    Appearance coordSysApp = new Appearance();
    LineAttributes coordSysLa = new LineAttributes();
    coordSysLa.setLineWidth(3.0f);
    coordSysApp.setLineAttributes(coordSysLa);
    ColoringAttributes coordSysCa = new ColoringAttributes(grey,
        ColoringAttributes.SHADE_FLAT);
    coordSysApp.setColoringAttributes(coordSysCa);
    Point3f[] coordSysPts = new Point3f[4];
    coordSysPts[0] = new Point3f(gridMinX, 0, -0.5f);
    coordSysPts[1] = new Point3f(gridMaxX, 0, -0.5f);
    coordSysPts[2] = new Point3f(0, gridMinY, -0.5f);
    coordSysPts[3] = new Point3f(0, gridMaxY, -0.5f);
    LineArray coordSysLines = new LineArray(4, LineArray.COORDINATES);
    coordSysLines.setCoordinates(0, coordSysPts);
    Shape3D coordSysShape = new Shape3D(coordSysLines, coordSysApp);
    objTrans.addChild(coordSysShape);

    // set up the circle
    Appearance circleApp = new Appearance();
    ColoringAttributes circleCa = new ColoringAttributes();
    circleCa.setColor(red);
    circleApp.setColoringAttributes(circleCa);
    PolygonAttributes pa = new PolygonAttributes();
    pa.setCullFace(PolygonAttributes.CULL_NONE);
    circleApp.setPolygonAttributes(pa);
    int step = 360 / (numCirclePts - 1);
    for (int deg = 0; deg < 360; deg += step) {
      double angle = Math.toRadians(deg);
      circlePtsVW[deg / 10].x = sphereRadius * (float) Math.sin(angle);
      circlePtsVW[deg / 10].y = sphereRadius * (float) Math.cos(angle);
      circlePtsVW[deg / 10].z = -0.3f;
    }
    circlePtsVW[numCirclePts - 1].set(circlePtsVW[0]);
    int[] lineStripLength = new int[1];
    lineStripLength[0] = numCirclePts;
    //LineStripArray circleLineStrip = new LineStripArray(numCirclePts,
    //        LineArray.COORDINATES, lineStripLength);
    TriangleFanArray circleLineStrip = new TriangleFanArray(numCirclePts,
        LineArray.COORDINATES, lineStripLength);
    circleLineStrip.setCoordinates(0, circlePtsVW);
    Shape3D circleShape = new Shape3D(circleLineStrip, circleApp);
    objTrans.addChild(circleShape);

    return objRoot;
  }

  // return the closest multiple of step less than value
  float gridStepFloor(float value, float step) {
    return (float) (step * (Math.floor(value / step)));
  }

  // return the closest multiple of step greater than value
  float gridStepCeil(float value, float step) {
    return (float) (step * (Math.ceil(value / step)));
  }

  public BranchGroup createProjViewSG() {
    // Create the root of the branch graph
    BranchGroup objRoot = new BranchGroup();
    objRoot.setCapability(BranchGroup.ALLOW_DETACH);

    // setup a transform group to hold the scaled scene
    TransformGroup objTrans = new TransformGroup();
    Transform3D scale = new Transform3D();
    scale.set(0.9);
    objTrans.setTransform(scale);
    objRoot.addChild(objTrans);

    // create the clip limits line
    Point3f[] cpPoints = new Point3f[5];
    cpPoints[0] = new Point3f(-1, -1, 0.1f);
    cpPoints[1] = new Point3f(1, -1, 0.1f);
    cpPoints[2] = new Point3f(1, 1, 0.1f);
    cpPoints[3] = new Point3f(-1, 1, 0.1f);
    cpPoints[4] = cpPoints[0];
    int[] cpLength = new int[1];
    cpLength[0] = 5;
    LineStripArray cpLines = new LineStripArray(5, LineArray.COORDINATES,
        cpLength);
    cpLines.setCoordinates(0, cpPoints);
    Appearance cpApp = new Appearance();
    ColoringAttributes cpCa = new ColoringAttributes(blue,
        ColoringAttributes.SHADE_FLAT);
    cpApp.setColoringAttributes(cpCa);
    LineAttributes cpLa = new LineAttributes();
    Shape3D cpShape = new Shape3D(cpLines, cpApp);
    objTrans.addChild(cpShape);

    // transform and render the clip grid points
    updateProjTrans();

    if (numClipGridPts > 0) {
      // transform the clipGridPts
      for (int i = 0; i < numClipGridPts; i++) {
        projectPoint(clipGridPtsVW[i], clipGridPtsProj[i]);
      }

      LineArray clipLn = new LineArray(numClipGridPts,
          LineArray.COORDINATES);
      clipLn.setCoordinates(0, clipGridPtsProj, 0, numClipGridPts);
      Appearance clipGridApp = new Appearance();
      ColoringAttributes clipCa = new ColoringAttributes(black,
          ColoringAttributes.SHADE_FLAT);
      clipGridApp.setColoringAttributes(clipCa);
      LineAttributes clipLa = new LineAttributes();
      Shape3D clipShape = new Shape3D(clipLn, clipGridApp);
      objTrans.addChild(clipShape);
    }

    // set up the circle
    Appearance circleApp = new Appearance();
    ColoringAttributes circleCa = new ColoringAttributes();
    circleCa.setColor(red);
    circleApp.setColoringAttributes(circleCa);
    PolygonAttributes pa = new PolygonAttributes();
    pa.setCullFace(PolygonAttributes.CULL_NONE);
    circleApp.setPolygonAttributes(pa);

    // transform the circlePts
    for (int i = 0; i < numCirclePts; i++) {
      projectPoint(circlePtsVW[i], circlePtsProj[i]);
    }

    int[] lineStripLength = new int[1];
    lineStripLength[0] = numCirclePts;
    //LineStripArray circleLineStrip = new LineStripArray(numCirclePts,
    //        LineArray.COORDINATES, lineStripLength);
    TriangleFanArray circleLineStrip = new TriangleFanArray(numCirclePts,
        LineArray.COORDINATES, lineStripLength);
    circleLineStrip.setCoordinates(0, circlePtsProj);
    Shape3D circleShape = new Shape3D(circleLineStrip, circleApp);
    objTrans.addChild(circleShape);

    return objRoot;
  }

  void projectPoint(Point3f ptVW, Point3f ptProj) {
    // handle the VW having y and z switched
    // TODO: fix viewpoint for views
    projPt.x = ptVW.x;
    projPt.y = ptVW.z;
    projPt.z = -ptVW.y;
    projPt.w = 1.0f;

    projPt.z += eyePtVW.z; // TODO: move to projTrans

    //System.out.println("projPtVW = (" +
    //  projPt.x + ", " +
    //  projPt.y + ", " +
    //  projPt.z + ")");

    projTrans.transform(projPt);
    projPt.x /= projPt.w;
    projPt.y /= projPt.w;
    projPt.z /= projPt.w;

    //System.out.println("projPt = (" +
    //  projPt.x + ", " +
    //  projPt.y + ", " +
    //  projPt.z + ")");

    ptProj.x = (float) projPt.x;
    ptProj.y = (float) projPt.z;
    ptProj.z = (float) projPt.y;
  }

  /**
   * Calculates the projection transform specified by the field of view and
   * clip distances specified by the view.
   */
  public void updateProjTrans() {
    int projType = view.getProjectionPolicy();
    if (projType == View.PARALLEL_PROJECTION) {
      //System.out.println("PARALLEL_PROJECTION");
      projTrans.setIdentity();
      return;
    }
    //System.out.println("PERSPECTIVE_PROJECTION");
    // figure out the perspective transform from the view
    double fov = view.getFieldOfView();

    // n = near clip
    double n = frontClipDist;
    // f = far clip
    double f = backClipDist;

    //System.out.println("n = " + nf.format(n) + " f = " + nf.format(f));

    // Create a matrix using coefficents derived from the OpenGL
    // glFrustum() man page. This assumes the eye point is a 0,0,0,
    // the front clip plane is a z = -n, the back clip plane is at
    // z = -f and that the front clip plane intersects the FOV so that
    // -1 <= X,Y <= 1 at the front plane (the last assumption may not
    // be true, so we'll scale later).
    Matrix4d matrix = new Matrix4d();
    matrix.m00 = n;
    matrix.m11 = n;
    matrix.m22 = -(f + n) / (f - n);
    matrix.m23 = -2 * f * n / (f - n);
    matrix.m32 = -1;

    //System.out.println("matrix = " + matrix);

    // This is the distance where the FOV maps to a -1 to 1 area in X and Y
    double d = 1 / Math.tan(fov / 2);

    //System.out.println("n = " + nf.format(n) + " f = " + nf.format(f) +
    //  " d = " + nf.format(d));

    // this is a scaling ratio to make the OpenGL glFrustum() matrix
    // elements work with with the J3D matrix. It compensates for the
    // front clip plane not being at the FOV distance (the OpenGL
    // matrix expects n == d).
    double scale = n / d;
    //System.out.println("scale = " + nf.format(scale));

    // scale the elements of the matrix
    //matrix.m00 *= 1.0/scale;
    //matrix.m11 *= 1.0/scale;
    matrix.m22 *= scale;
    matrix.m23 *= scale;
    matrix.m32 *= scale;

    // set the Transform3D
    projTrans.set(matrix);

    //System.out.println("projTrans = " + projTrans);
  }

  /* TODO: use a behavior post to avoid the flicker when these change */
  void updateViewWindows() {
    BranchGroup newUlScene = createVWorldViewSG();
    urScene.detach();
    urUniverse.addBranchGraph(newUlScene);
    urScene = newUlScene;

    BranchGroup newLlScene = createProjViewSG();
    lrScene.detach();
    lrUniverse.addBranchGraph(newLlScene);
    lrScene = newLlScene;
  }

  public ViewProj() {
    this(true);
  }

  public ViewProj(boolean isApplication) {
    this.isApplication = isApplication;
  }

  public void init() {
    setLayout(new BorderLayout());

    nf = NumberFormat.getInstance();
    nf.setMaximumFractionDigits(3);

    GraphicsConfiguration config = SimpleUniverse
        .getPreferredConfiguration();

    JPanel canvasPanel = new JPanel();
    GridBagLayout gridbag = new GridBagLayout();
    canvasPanel.setLayout(gridbag);

    canvas = new Canvas3D(config);
    canvas.setSize(400, 400);

    GridBagConstraints constraints = new GridBagConstraints();
    constraints.gridx = 0;
    constraints.gridy = 0;
    constraints.gridwidth = 2;
    constraints.gridheight = 2;
    constraints.insets = new Insets(5, 5, 5, 5);
    constraints.fill = GridBagConstraints.BOTH;
    gridbag.setConstraints(canvas, constraints);
    canvasPanel.add(canvas);

    constraints.fill = GridBagConstraints.REMAINDER;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.gridx = 2;
    constraints.gridy = 0;
    urCanvas = new Canvas3D(config);
    urCanvas.setSize(200, 200);
    gridbag.setConstraints(urCanvas, constraints);
    canvasPanel.add(urCanvas);

    constraints.gridx = 2;
    constraints.gridy = 1;
    lrCanvas = new Canvas3D(config);
    lrCanvas.setSize(200, 200);
    gridbag.setConstraints(lrCanvas, constraints);
    canvasPanel.add(lrCanvas);

    add(canvasPanel, BorderLayout.NORTH);

    SimpleUniverse u = new SimpleUniverse(canvas);
    urUniverse = new SimpleUniverse(urCanvas);
    lrUniverse = new SimpleUniverse(lrCanvas);

    if (isApplication) {
      offScreenCanvas = new OffScreenCanvas3D(config, true);
      // set the size of the off-screen canvas based on a scale
      // of the on-screen size
      Screen3D sOn = canvas.getScreen3D();
      Screen3D sOff = offScreenCanvas.getScreen3D();
      Dimension dim = sOn.getSize();
      dim.width *= offScreenScale;
      dim.height *= offScreenScale;
      sOff.setSize(dim);
      sOff.setPhysicalScreenWidth(sOn.getPhysicalScreenWidth()
          * offScreenScale);
      sOff.setPhysicalScreenHeight(sOn.getPhysicalScreenHeight()
          * offScreenScale);

      // attach the offscreen canvas to the view
      u.getViewer().getView().addCanvas3D(offScreenCanvas);

      urOffScreenCanvas = new OffScreenCanvas3D(config, true);
      // set the size of the off-screen canvas based on a scale
      // of the on-screen size
      sOn = urCanvas.getScreen3D();
      sOff = urOffScreenCanvas.getScreen3D();
      dim = sOn.getSize();
      dim.width *= urOffScreenScale;
      dim.height *= urOffScreenScale;
      sOff.setSize(dim);
      sOff.setPhysicalScreenWidth(sOn.getPhysicalScreenWidth()
          * urOffScreenScale);
      sOff.setPhysicalScreenHeight(sOn.getPhysicalScreenHeight()
          * urOffScreenScale);

      // attach the offscreen canvas to the view
      urUniverse.getViewer().getView().addCanvas3D(urOffScreenCanvas);

      lrOffScreenCanvas = new OffScreenCanvas3D(config, true);
      // set the size of the off-screen canvas based on a scale
      // of the on-screen size
      sOn = lrCanvas.getScreen3D();
      sOff = lrOffScreenCanvas.getScreen3D();
      dim = sOn.getSize();
      dim.width *= lrOffScreenScale;
      dim.height *= lrOffScreenScale;
      sOff.setSize(dim);
      sOff.setPhysicalScreenWidth(sOn.getPhysicalScreenWidth()
          * lrOffScreenScale);
      sOff.setPhysicalScreenHeight(sOn.getPhysicalScreenHeight()
          * lrOffScreenScale);

      // attach the offscreen canvas to the view
      lrUniverse.getViewer().getView().addCanvas3D(lrOffScreenCanvas);
    }

    // Create a simple scene and attach it to the virtual universe
    BranchGroup scene = createSceneGraph();

    // This will move the ViewPlatform back a bit so the
    // objects in the scene can be viewed.
    viewingPlatform = u.getViewingPlatform();
    viewingPlatform.setNominalViewingTransform();

    view = u.getViewer().getView();
    view.setFrontClipPolicy(View.VIRTUAL_EYE);
    view.setBackClipPolicy(View.VIRTUAL_EYE);
    view.setFrontClipDistance(frontClipDist);
    view.setBackClipDistance(backClipDist);

    u.addBranchGraph(scene);

    // init the clipGridPts arrays
    for (int i = 0; i < maxClipGridPts; i++) {
      clipGridPtsVW[i] = new Point3f();
      clipGridPtsProj[i] = new Point3f();
    }

    // init the circlePts arrays
    for (int i = 0; i < numCirclePts; i++) {
      circlePtsVW[i] = new Point3f();
      circlePtsProj[i] = new Point3f();
    }

    // setup the ur canvas
    urScene = createVWorldViewSG();
    // This will move the ViewPlatform back a bit so the
    // objects in the scene can be viewed.
    urUniverse.getViewingPlatform().setNominalViewingTransform();
    View urView = urUniverse.getViewer().getView();
    urView.setProjectionPolicy(View.PARALLEL_PROJECTION);
    urUniverse.addBranchGraph(urScene);
    // set up the background on a separate BG so that it can stay there
    // when we replace the scene SG
    Background urBgWhite = new Background(white);
    urBgWhite.setApplicationBounds(infiniteBounds);
    BranchGroup urBackBG = new BranchGroup();
    urBackBG.addChild(urBgWhite);
    urUniverse.addBranchGraph(urBackBG);

    // setup the lr canvas
    lrScene = createProjViewSG();
    // This will move the ViewPlatform back a bit so the
    // objects in the scene can be viewed.
    lrUniverse.getViewingPlatform().setNominalViewingTransform();
    View lrView = lrUniverse.getViewer().getView();
    lrView.setProjectionPolicy(View.PARALLEL_PROJECTION);
    lrUniverse.addBranchGraph(lrScene);
    // set up the background on a separate BG so that it can stay there
    // when we replace the scene SG
    Background lrBgWhite = new Background(white);
    lrBgWhite.setApplicationBounds(infiniteBounds);
    BranchGroup lrBackBG = new BranchGroup();
    lrBackBG.addChild(lrBgWhite);
    lrUniverse.addBranchGraph(lrBackBG);

    // set up the sliders
    JPanel guiPanel = new JPanel();
    guiPanel.setLayout(new GridLayout(0, 2));
    FloatLabelJSlider dynamicSlider = new FloatLabelJSlider(
        "Dynamic Offset", 0.1f, 0.0f, 2.0f, dynamicOffset);
    dynamicSlider.addFloatListener(new FloatListener() {
      public void floatChanged(FloatEvent e) {
        dynamicOffset = e.getValue();
        solidPa.setPolygonOffsetFactor(dynamicOffset);
      }
    });
    guiPanel.add(dynamicSlider);

    LogFloatLabelJSlider staticSlider = new LogFloatLabelJSlider(
        "Static Offset", 0.1f, 10000.0f, staticOffset);
    staticSlider.addFloatListener(new FloatListener() {
      public void floatChanged(FloatEvent e) {
        staticOffset = e.getValue();
        solidPa.setPolygonOffset(staticOffset);
      }
    });
    guiPanel.add(staticSlider);

    // These are declared final here so they can be changed by the
    // listener routines below.
    LogFloatLabelJSlider frontClipSlider = new LogFloatLabelJSlider(
        "Front Clip Distance", 0.001f, 10.0f, frontClipDist);
    final LogFloatLabelJSlider backClipSlider = new LogFloatLabelJSlider(
        "Back Clip Distance", 1.0f, 10000.0f, backClipDist);
    final LogFloatLabelJSlider backClipRatioSlider = new LogFloatLabelJSlider(
        "Back Clip Ratio", 1.0f, 10000.0f, backClipRatio);

    frontClipSlider.addFloatListener(new FloatListener() {
      public void floatChanged(FloatEvent e) {
        frontClipDist = e.getValue();
        view.setFrontClipDistance(frontClipDist);
        backClipRatio = backClipDist / frontClipDist;
        backClipRatioSlider.setValue(backClipRatio);
        updateViewWindows();
      }
    });
    guiPanel.add(frontClipSlider);

    backClipSlider.addFloatListener(new FloatListener() {
      public void floatChanged(FloatEvent e) {
        backClipDist = e.getValue();
        backClipRatio = backClipDist / frontClipDist;
        backClipRatioSlider.setValue(backClipRatio);
        view.setBackClipDistance(backClipDist);
        updateViewWindows();
      }
    });
    guiPanel.add(backClipSlider);

    backClipRatioSlider.addFloatListener(new FloatListener() {
      public void floatChanged(FloatEvent e) {
        backClipRatio = e.getValue();
        backClipDist = backClipRatio * frontClipDist;
        backClipSlider.setValue(backClipDist);
        updateViewWindows();
      }
    });
    guiPanel.add(backClipRatioSlider);
    FloatLabelJSlider innerSphereSlider = new FloatLabelJSlider(
        "Inner Sphere Scale", 0.001f, 0.90f, 1.0f, innerScale);
    innerSphereSlider.addFloatListener(new FloatListener() {
      public void floatChanged(FloatEvent e) {
        innerScale = e.getValue();
        updateInnerScale();
      }
    });
    guiPanel.add(innerSphereSlider);

    JButton mainSnap = new JButton(snapImageString);
    mainSnap.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        Point loc = canvas.getLocationOnScreen();
        offScreenCanvas.setOffScreenLocation(loc);
        Dimension dim = canvas.getSize();
        dim.width *= offScreenScale;
        dim.height *= offScreenScale;
        nf.setMinimumIntegerDigits(3);
        offScreenCanvas.snapImageFile(outFileBase
            + nf.format(outFileSeq++), dim.width, dim.height);
        nf.setMinimumIntegerDigits(0);
      }
    });
    guiPanel.add(mainSnap);

    JButton urSnap = new JButton(urSnapImageString);
    urSnap.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        System.out.println("Snap UR");
        Point loc = urCanvas.getLocationOnScreen();
        urOffScreenCanvas.setOffScreenLocation(loc);
        Dimension dim = urCanvas.getSize();
        dim.width *= urOffScreenScale;
        dim.height *= urOffScreenScale;
        nf.setMinimumIntegerDigits(3);
        urOffScreenCanvas.snapImageFile(urOutFileBase
            + nf.format(urOutFileSeq++), dim.width, dim.height);
        nf.setMinimumIntegerDigits(0);
      }
    });
    guiPanel.add(urSnap);

    JButton lrSnap = new JButton(lrSnapImageString);
    lrSnap.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        System.out.println("Snap LR");
        Point loc = lrCanvas.getLocationOnScreen();
        lrOffScreenCanvas.setOffScreenLocation(loc);
        Dimension dim = lrCanvas.getSize();
        dim.width *= lrOffScreenScale;
        dim.height *= lrOffScreenScale;
        nf.setMinimumIntegerDigits(3);
        lrOffScreenCanvas.snapImageFile(lrOutFileBase
            + nf.format(lrOutFileSeq++), dim.width, dim.height);
        nf.setMinimumIntegerDigits(0);
      }
    });
    guiPanel.add(lrSnap);
    add(guiPanel, BorderLayout.SOUTH);
  }

  //
  // The following allows ViewProj to be run as an application
  // as well as an applet
  //
  public static void main(String[] args) {
    new MainFrame(new ViewProj(true), 700, 600);
  }
}

interface Java3DExplorerConstants {

  // colors
  static Color3f black = new Color3f(0.0f, 0.0f, 0.0f);

  static Color3f red = new Color3f(1.0f, 0.0f, 0.0f);

  static Color3f green = new Color3f(0.0f, 1.0f, 0.0f);

  static Color3f blue = new Color3f(0.0f, 0.0f, 1.0f);

  static Color3f skyBlue = new Color3f(0.6f, 0.7f, 0.9f);

  static Color3f cyan = new Color3f(0.0f, 1.0f, 1.0f);

  static Color3f magenta = new Color3f(1.0f, 0.0f, 1.0f);

  static Color3f yellow = new Color3f(1.0f, 1.0f, 0.0f);

  static Color3f brightWhite = new Color3f(1.0f, 1.5f, 1.5f);

  static Color3f white = new Color3f(1.0f, 1.0f, 1.0f);

  static Color3f darkGrey = new Color3f(0.15f, 0.15f, 0.15f);

  static Color3f medGrey = new Color3f(0.3f, 0.3f, 0.3f);

  static Color3f grey = new Color3f(0.5f, 0.5f, 0.5f);

  static Color3f lightGrey = new Color3f(0.75f, 0.75f, 0.75f);

  // infinite bounding region, used to make env nodes active everywhere
  BoundingSphere infiniteBounds = new BoundingSphere(new Point3d(),
      Double.MAX_VALUE);

  // common values
  static final String nicestString = "NICEST";

  static final String fastestString = "FASTEST";

  static final String antiAliasString = "Anti-Aliasing";

  static final String noneString = "NONE";

  // light type constants
  static int LIGHT_AMBIENT = 1;

  static int LIGHT_DIRECTIONAL = 2;

  static int LIGHT_POSITIONAL = 3;

  static int LIGHT_SPOT = 4;

  // screen capture constants
  static final int USE_COLOR = 1;

  static final int USE_BLACK_AND_WHITE = 2;

  // number formatter
  NumberFormat nf = NumberFormat.getInstance();

}

class OffScreenCanvas3D extends Canvas3D {

  OffScreenCanvas3D(GraphicsConfiguration graphicsConfiguration,
      boolean offScreen) {

    super(graphicsConfiguration, offScreen);
  }

  private BufferedImage doRender(int width, int height) {

    BufferedImage bImage = new BufferedImage(width, height,
        BufferedImage.TYPE_INT_RGB);

    ImageComponent2D buffer = new ImageComponent2D(
        ImageComponent.FORMAT_RGB, bImage);
    //buffer.setYUp(true);

    setOffScreenBuffer(buffer);
    renderOffScreenBuffer();
    waitForOffScreenRendering();
    bImage = getOffScreenBuffer().getImage();
    return bImage;
  }

  void snapImageFile(String filename, int width, int height) {
    BufferedImage bImage = doRender(width, height);

    /*
     * JAI: RenderedImage fImage = JAI.create("format", bImage,
     * DataBuffer.TYPE_BYTE); JAI.create("filestore", fImage, filename +
     * ".tif", "tiff", null);
     */

    /* No JAI: */
    try {
      FileOutputStream fos = new FileOutputStream(filename + ".jpg");
      BufferedOutputStream bos = new BufferedOutputStream(fos);

      JPEGImageEncoder jie = JPEGCodec.createJPEGEncoder(bos);
      JPEGEncodeParam param = jie.getDefaultJPEGEncodeParam(bImage);
      param.setQuality(1.0f, true);
      jie.setJPEGEncodeParam(param);
      jie.encode(bImage);

      bos.flush();
      fos.close();
    } catch (Exception e) {
      System.out.println(e);
    }
  }
}

class FloatLabelJSlider extends JPanel implements ChangeListener,
    Java3DExplorerConstants {

  JSlider slider;

  JLabel valueLabel;

  Vector listeners = new Vector();

  float min, max, resolution, current, scale;

  int minInt, maxInt, curInt;;

  int intDigits, fractDigits;

  float minResolution = 0.001f;

  // default slider with name, resolution = 0.1, min = 0.0, max = 1.0 inital
  // 0.5
  FloatLabelJSlider(String name) {
    this(name, 0.1f, 0.0f, 1.0f, 0.5f);
  }

  FloatLabelJSlider(String name, float resolution, float min, float max,
      float current) {

    this.resolution = resolution;
    this.min = min;
    this.max = max;
    this.current = current;

    if (resolution < minResolution) {
      resolution = minResolution;
    }

    // round scale to nearest integer fraction. i.e. 0.3 => 1/3 = 0.33
    scale = (float) Math.round(1.0f / resolution);
    resolution = 1.0f / scale;

    // get the integer versions of max, min, current
    minInt = Math.round(min * scale);
    maxInt = Math.round(max * scale);
    curInt = Math.round(current * scale);

    // sliders use integers, so scale our floating point value by "scale"
    // to make each slider "notch" be "resolution". We will scale the
    // value down by "scale" when we get the event.
    slider = new JSlider(JSlider.HORIZONTAL, minInt, maxInt, curInt);
    slider.addChangeListener(this);

    valueLabel = new JLabel(" ");

    // set the initial value label
    setLabelString();

    // add min and max labels to the slider
    Hashtable labelTable = new Hashtable();
    labelTable.put(new Integer(minInt), new JLabel(nf.format(min)));
    labelTable.put(new Integer(maxInt), new JLabel(nf.format(max)));
    slider.setLabelTable(labelTable);
    slider.setPaintLabels(true);

    /* layout to align left */
    setLayout(new BorderLayout());
    Box box = new Box(BoxLayout.X_AXIS);
    add(box, BorderLayout.WEST);

    box.add(new JLabel(name));
    box.add(slider);
    box.add(valueLabel);
  }

  public void setMinorTickSpacing(float spacing) {
    int intSpacing = Math.round(spacing * scale);
    slider.setMinorTickSpacing(intSpacing);
  }

  public void setMajorTickSpacing(float spacing) {
    int intSpacing = Math.round(spacing * scale);
    slider.setMajorTickSpacing(intSpacing);
  }

  public void setPaintTicks(boolean paint) {
    slider.setPaintTicks(paint);
  }

  public void addFloatListener(FloatListener listener) {
    listeners.add(listener);
  }

  public void removeFloatListener(FloatListener listener) {
    listeners.remove(listener);
  }

  public void stateChanged(ChangeEvent e) {
    JSlider source = (JSlider) e.getSource();
    // get the event type, set the corresponding value.
    // Sliders use integers, handle floating point values by scaling the
    // values by "scale" to allow settings at "resolution" intervals.
    // Divide by "scale" to get back to the real value.
    curInt = source.getValue();
    current = curInt / scale;

    valueChanged();
  }

  public void setValue(float newValue) {
    boolean changed = (newValue != current);
    current = newValue;
    if (changed) {
      valueChanged();
    }
  }

  private void valueChanged() {
    // update the label
    setLabelString();

    // notify the listeners
    FloatEvent event = new FloatEvent(this, current);
    for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
      FloatListener listener = (FloatListener) e.nextElement();
      listener.floatChanged(event);
    }
  }

  void setLabelString() {
    // Need to muck around to try to make sure that the width of the label
    // is wide enough for the largest value. Pad the string
    // be large enough to hold the largest value.
    int pad = 5; // fudge to make up for variable width fonts
    float maxVal = Math.max(Math.abs(min), Math.abs(max));
    intDigits = Math.round((float) (Math.log(maxVal) / Math.log(10))) + pad;
    if (min < 0) {
      intDigits++; // add one for the '-'
    }
    // fractDigits is num digits of resolution for fraction. Use base 10 log
    // of scale, rounded up, + 2.
    fractDigits = (int) Math.ceil((Math.log(scale) / Math.log(10)));
    nf.setMinimumFractionDigits(fractDigits);
    nf.setMaximumFractionDigits(fractDigits);
    String value = nf.format(current);
    while (value.length() < (intDigits + fractDigits)) {
      value = value + "  ";
    }
    valueLabel.setText(value);
  }

}

class FloatEvent extends EventObject {

  float value;

  FloatEvent(Object source, float newValue) {
    super(source);
    value = newValue;
  }

  float getValue() {
    return value;
  }
}

interface FloatListener extends EventListener {
  void floatChanged(FloatEvent e);
}



class LogFloatLabelJSlider extends JPanel implements ChangeListener,
    Java3DExplorerConstants {

  JSlider slider;

  JLabel valueLabel;

  Vector listeners = new Vector();

  float min, max, resolution, current, scale;

  double minLog, maxLog, curLog;

  int minInt, maxInt, curInt;;

  int intDigits, fractDigits;

  NumberFormat nf = NumberFormat.getInstance();

  float minResolution = 0.001f;

  double logBase = Math.log(10);

  // default slider with name, resolution = 0.1, min = 0.0, max = 1.0 inital
  // 0.5
  LogFloatLabelJSlider(String name) {
    this(name, 0.1f, 100.0f, 10.0f);
  }

  LogFloatLabelJSlider(String name, float min, float max, float current) {

    this.resolution = resolution;
    this.min = min;
    this.max = max;
    this.current = current;

    if (resolution < minResolution) {
      resolution = minResolution;
    }

    minLog = log10(min);
    maxLog = log10(max);
    curLog = log10(current);

    // resolution is 100 steps from min to max
    scale = 100.0f;
    resolution = 1.0f / scale;

    // get the integer versions of max, min, current
    minInt = (int) Math.round(minLog * scale);
    maxInt = (int) Math.round(maxLog * scale);
    curInt = (int) Math.round(curLog * scale);

    slider = new JSlider(JSlider.HORIZONTAL, minInt, maxInt, curInt);
    slider.addChangeListener(this);

    valueLabel = new JLabel(" ");

    // Need to muck around to make sure that the width of the label
    // is wide enough for the largest value. Pad the initial string
    // be large enough to hold the largest value.
    int pad = 5; // fudge to make up for variable width fonts
    intDigits = (int) Math.ceil(maxLog) + pad;
    if (min < 0) {
      intDigits++; // add one for the '-'
    }
    if (minLog < 0) {
      fractDigits = (int) Math.ceil(-minLog);
    } else {
      fractDigits = 0;
    }
    nf.setMinimumFractionDigits(fractDigits);
    nf.setMaximumFractionDigits(fractDigits);
    String value = nf.format(current);
    while (value.length() < (intDigits + fractDigits)) {
      value = value + " ";
    }
    valueLabel.setText(value);

    // add min and max labels to the slider
    Hashtable labelTable = new Hashtable();
    labelTable.put(new Integer(minInt), new JLabel(nf.format(min)));
    labelTable.put(new Integer(maxInt), new JLabel(nf.format(max)));
    slider.setLabelTable(labelTable);
    slider.setPaintLabels(true);

    // layout to align left
    setLayout(new BorderLayout());
    Box box = new Box(BoxLayout.X_AXIS);
    add(box, BorderLayout.WEST);

    box.add(new JLabel(name));
    box.add(slider);
    box.add(valueLabel);
  }

  public void setMinorTickSpacing(float spacing) {
    int intSpacing = Math.round(spacing * scale);
    slider.setMinorTickSpacing(intSpacing);
  }

  public void setMajorTickSpacing(float spacing) {
    int intSpacing = Math.round(spacing * scale);
    slider.setMajorTickSpacing(intSpacing);
  }

  public void setPaintTicks(boolean paint) {
    slider.setPaintTicks(paint);
  }

  public void addFloatListener(FloatListener listener) {
    listeners.add(listener);
  }

  public void removeFloatListener(FloatListener listener) {
    listeners.remove(listener);
  }

  public void stateChanged(ChangeEvent e) {
    JSlider source = (JSlider) e.getSource();
    curInt = source.getValue();
    curLog = curInt / scale;
    current = (float) exp10(curLog);

    valueChanged();
  }

  public void setValue(float newValue) {
    boolean changed = (newValue != current);
    current = newValue;
    if (changed) {
      valueChanged();
    }
  }

  private void valueChanged() {
    String value = nf.format(current);
    valueLabel.setText(value);

    // notify the listeners
    FloatEvent event = new FloatEvent(this, current);
    for (Enumeration e = listeners.elements(); e.hasMoreElements();) {
      FloatListener listener = (FloatListener) e.nextElement();
      listener.floatChanged(event);
    }
  }

  double log10(double value) {
    return Math.log(value) / logBase;
  }

  double exp10(double value) {
    return Math.exp(value * logBase);
  }

}



           
       








Related examples in the same category

1.Appearance ExplorerAppearance Explorer
2.PolygonOffsetPolygonOffset