Transform Explorer
/*
* %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.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.awt.GridLayout;
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.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.Font3D;
import javax.media.j3d.ImageComponent;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.Link;
import javax.media.j3d.Material;
import javax.media.j3d.OrientedShape3D;
import javax.media.j3d.Screen3D;
import javax.media.j3d.SharedGroup;
import javax.media.j3d.Switch;
import javax.media.j3d.Text3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.View;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.vecmath.AxisAngle4f;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;
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.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.geometry.Cone;
import com.sun.j3d.utils.geometry.Cylinder;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.ViewingPlatform;
/*
*
*/
public class TransformExplorer extends Applet implements
Java3DExplorerConstants {
SimpleUniverse u;
boolean isApplication;
Canvas3D canvas;
OffScreenCanvas3D offScreenCanvas;
View view;
TransformGroup coneTG;
// transformation factors for the cone
Vector3f coneTranslation = new Vector3f(0.0f, 0.0f, 0.0f);
float coneScale = 1.0f;
Vector3d coneNUScale = new Vector3d(1.0f, 1.0f, 1.0f);
Vector3f coneRotateAxis = new Vector3f(1.0f, 0.0f, 0.0f);
Vector3f coneRotateNAxis = new Vector3f(1.0f, 0.0f, 0.0f);
float coneRotateAngle = 0.0f;
AxisAngle4f coneRotateAxisAngle = new AxisAngle4f(coneRotateAxis,
coneRotateAngle);
Vector3f coneRefPt = new Vector3f(0.0f, 0.0f, 0.0f);
// this tells whether to use the compound transformation
boolean useCompoundTransform = true;
// These are Transforms are used for the compound transformation
Transform3D translateTrans = new Transform3D();
Transform3D scaleTrans = new Transform3D();
Transform3D rotateTrans = new Transform3D();
Transform3D refPtTrans = new Transform3D();
Transform3D refPtInvTrans = new Transform3D();
// this tells whether to use the uniform or non-uniform scale when
// updating the compound transform
boolean useUniformScale = true;
// The size of the cone
float coneRadius = 1.0f;
float coneHeight = 2.0f;
// The axis indicator, used to show the rotation axis
RotAxis rotAxis;
boolean showRotAxis = false;
float rotAxisLength = 3.0f;
// The coord sys used to show the coordinate system
CoordSys coordSys;
boolean showCoordSys = true;
float coordSysLength = 5.0f;
// GUI elements
String rotAxisString = "Rotation Axis";
String coordSysString = "Coord Sys";
JCheckBox rotAxisCheckBox;
JCheckBox coordSysCheckBox;
String snapImageString = "Snap Image";
String outFileBase = "transform";
int outFileSeq = 0;
float offScreenScale;
JLabel coneRotateNAxisXLabel;
JLabel coneRotateNAxisYLabel;
JLabel coneRotateNAxisZLabel;
// Temporaries that are reused
Transform3D tmpTrans = new Transform3D();
Vector3f tmpVector = new Vector3f();
AxisAngle4f tmpAxisAngle = new AxisAngle4f();
// geometric constant
Point3f origin = new Point3f();
Vector3f yAxis = new Vector3f(0.0f, 1.0f, 0.0f);
// Returns the TransformGroup we will be editing to change the transform
// on the cone
TransformGroup createConeTransformGroup() {
// create a TransformGroup for the cone, allow tranform changes,
coneTG = new TransformGroup();
coneTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
coneTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
// Set up an appearance to make the Cone with red ambient,
// black emmissive, red diffuse and white specular coloring
Material material = new Material(red, black, red, white, 64);
// These are the colors used for the book figures:
//Material material = new Material(white, black, white, black, 64);
Appearance appearance = new Appearance();
appearance.setMaterial(material);
// create the cone and add it to the coneTG
Cone cone = new Cone(coneRadius, coneHeight, appearance);
coneTG.addChild(cone);
return coneTG;
}
void setConeTranslation() {
coneTG.getTransform(tmpTrans); // get the old transform
tmpTrans.setTranslation(coneTranslation); // set only translation
coneTG.setTransform(tmpTrans); // set the new transform
}
void setConeUScale() {
coneTG.getTransform(tmpTrans); // get the old transform
tmpTrans.setScale(coneScale); // set only scale
coneTG.setTransform(tmpTrans); // set the new transform
}
void setConeNUScale() {
coneTG.getTransform(tmpTrans); // get the old transform
System.out.println("coneNUScale.x = " + coneNUScale.x);
tmpTrans.setScale(coneNUScale);// set only scale
coneTG.setTransform(tmpTrans); // set the new transform
}
void setConeRotation() {
coneTG.getTransform(tmpTrans); // get the old transform
tmpTrans.setRotation(coneRotateAxisAngle); // set only rotation
coneTG.setTransform(tmpTrans); // set the new transform
}
void updateUsingCompoundTransform() {
// set the component transformations
translateTrans.set(coneTranslation);
if (useUniformScale) {
scaleTrans.set(coneScale);
} else {
scaleTrans.setIdentity();
scaleTrans.setScale(coneNUScale);
}
rotateTrans.set(coneRotateAxisAngle);
// translate from ref pt to origin
tmpVector.sub(origin, coneRefPt); // vector from ref pt to origin
refPtTrans.set(tmpVector);
// translate from origin to ref pt
tmpVector.sub(coneRefPt, origin); // vector from origin to ref pt
refPtInvTrans.set(tmpVector);
// now build up the transfomation
// trans = translate * refPtInv * scale * rotate * refPt;
tmpTrans.set(translateTrans);
tmpTrans.mul(refPtInvTrans);
tmpTrans.mul(scaleTrans);
tmpTrans.mul(rotateTrans);
tmpTrans.mul(refPtTrans);
// Copy the transform to the TransformGroup
coneTG.setTransform(tmpTrans);
}
// ensure that the cone rotation axis is a unit vector
void normalizeConeRotateAxis() {
// normalize, watch for length == 0, if so, then use default
float lengthSquared = coneRotateAxis.lengthSquared();
if (lengthSquared > 0.0001) {
coneRotateNAxis.scale((float) (1.0 / Math.sqrt(lengthSquared)),
coneRotateAxis);
} else {
coneRotateNAxis.set(1.0f, 0.0f, 0.0f);
}
}
// copy the current axis and angle to the axis angle, convert angle
// to radians
void updateConeAxisAngle() {
coneRotateAxisAngle.set(coneRotateNAxis, (float) Math
.toRadians(coneRotateAngle));
}
void updateConeRotateNormalizedLabels() {
nf.setMinimumFractionDigits(2);
nf.setMaximumFractionDigits(2);
coneRotateNAxisXLabel.setText("X: " + nf.format(coneRotateNAxis.x));
coneRotateNAxisYLabel.setText("Y: " + nf.format(coneRotateNAxis.y));
coneRotateNAxisZLabel.setText("Z: " + nf.format(coneRotateNAxis.z));
}
BranchGroup createSceneGraph() {
// Create the root of the branch graph
BranchGroup objRoot = new BranchGroup();
// Create a TransformGroup to scale the scene down by 3.5x
TransformGroup objScale = new TransformGroup();
Transform3D scaleTrans = new Transform3D();
scaleTrans.set(1 / 3.5f); // scale down by 3.5x
objScale.setTransform(scaleTrans);
objRoot.addChild(objScale);
// Create a TransformGroup and initialize it to the
// identity. Enable the TRANSFORM_WRITE capability so that
// the mouse behaviors code can modify it at runtime. Add it to the
// root of the subgraph.
TransformGroup objTrans = new TransformGroup();
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
objScale.addChild(objTrans);
// Add the primitives to the scene
objTrans.addChild(createConeTransformGroup()); // the cone
rotAxis = new RotAxis(rotAxisLength); // the axis
objTrans.addChild(rotAxis);
coordSys = new CoordSys(coordSysLength); // the coordSys
objTrans.addChild(coordSys);
BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
// The book used a white background for the figures
//Background bg = new Background(new Color3f(1.0f, 1.0f, 1.0f));
//bg.setApplicationBounds(bounds);
//objTrans.addChild(bg);
// Set up the ambient light
Color3f ambientColor = new Color3f(0.1f, 0.1f, 0.1f);
AmbientLight ambientLightNode = new AmbientLight(ambientColor);
ambientLightNode.setInfluencingBounds(bounds);
objRoot.addChild(ambientLightNode);
// Set up the directional lights
Color3f light1Color = new Color3f(1.0f, 1.0f, 1.0f);
Vector3f light1Direction = new Vector3f(0.0f, -0.2f, -1.0f);
DirectionalLight light1 = new DirectionalLight(light1Color,
light1Direction);
light1.setInfluencingBounds(bounds);
objRoot.addChild(light1);
return objRoot;
}
public TransformExplorer() {
this(false, 1.0f);
}
public TransformExplorer(boolean isApplication, float initOffScreenScale) {
this.isApplication = isApplication;
this.offScreenScale = initOffScreenScale;
}
public void init() {
setLayout(new BorderLayout());
GraphicsConfiguration config = SimpleUniverse
.getPreferredConfiguration();
canvas = new Canvas3D(config);
add("Center", canvas);
u = new SimpleUniverse(canvas);
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);
}
// Create a simple scene and attach it to the virtual universe
BranchGroup scene = createSceneGraph();
// get the view
view = u.getViewer().getView();
// This will move the ViewPlatform back a bit so the
// objects in the scene can be viewed.
ViewingPlatform viewingPlatform = u.getViewingPlatform();
viewingPlatform.setNominalViewingTransform();
// add an orbit behavior to move the viewing platform
OrbitBehavior orbit = new OrbitBehavior(canvas, OrbitBehavior.STOP_ZOOM);
BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
100.0);
orbit.setSchedulingBounds(bounds);
viewingPlatform.setViewPlatformBehavior(orbit);
u.addBranchGraph(scene);
add("East", guiPanel());
}
// create a panel with a tabbed pane holding each of the edit panels
JPanel guiPanel() {
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.addTab("Translation", translationPanel());
tabbedPane.addTab("Scaling", scalePanel());
tabbedPane.addTab("Rotation", rotationPanel());
tabbedPane.addTab("Reference Point", refPtPanel());
panel.add("Center", tabbedPane);
panel.add("South", configPanel());
return panel;
}
Box translationPanel() {
Box panel = new Box(BoxLayout.Y_AXIS);
panel.add(new LeftAlignComponent(new JLabel("Translation Offset")));
// X translation label, slider, and value label
FloatLabelJSlider coneTranslateXSlider = new FloatLabelJSlider("X",
0.1f, -2.0f, 2.0f, coneTranslation.x);
coneTranslateXSlider.setMajorTickSpacing(1.0f);
coneTranslateXSlider.setPaintTicks(true);
coneTranslateXSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneTranslation.x = e.getValue();
if (useCompoundTransform) {
updateUsingCompoundTransform();
} else {
setConeTranslation();
}
}
});
panel.add(coneTranslateXSlider);
// Y translation label, slider, and value label
FloatLabelJSlider coneTranslateYSlider = new FloatLabelJSlider("Y",
0.1f, -2.0f, 2.0f, coneTranslation.y);
coneTranslateYSlider.setMajorTickSpacing(1.0f);
coneTranslateYSlider.setPaintTicks(true);
coneTranslateYSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneTranslation.y = e.getValue();
if (useCompoundTransform) {
updateUsingCompoundTransform();
} else {
setConeTranslation();
}
}
});
panel.add(coneTranslateYSlider);
// Z translation label, slider, and value label
FloatLabelJSlider coneTranslateZSlider = new FloatLabelJSlider("Z",
0.1f, -2.0f, 2.0f, coneTranslation.z);
coneTranslateZSlider.setMajorTickSpacing(1.0f);
coneTranslateZSlider.setPaintTicks(true);
coneTranslateZSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneTranslation.z = e.getValue();
if (useCompoundTransform) {
updateUsingCompoundTransform();
} else {
setConeTranslation();
}
}
});
panel.add(coneTranslateZSlider);
return panel;
}
Box scalePanel() {
Box panel = new Box(BoxLayout.Y_AXIS);
// Uniform Scale
JLabel uniform = new JLabel("Uniform Scale");
panel.add(new LeftAlignComponent(uniform));
FloatLabelJSlider coneScaleSlider = new FloatLabelJSlider("S:", 0.1f,
0.0f, 3.0f, coneScale);
coneScaleSlider.setMajorTickSpacing(1.0f);
coneScaleSlider.setPaintTicks(true);
coneScaleSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneScale = e.getValue();
useUniformScale = true;
if (useCompoundTransform) {
updateUsingCompoundTransform();
} else {
setConeUScale();
}
}
});
panel.add(coneScaleSlider);
JLabel nonUniform = new JLabel("Non-Uniform Scale");
panel.add(new LeftAlignComponent(nonUniform));
// Non-Uniform Scale
FloatLabelJSlider coneNUScaleXSlider = new FloatLabelJSlider("X: ",
0.1f, 0.0f, 3.0f, (float) coneNUScale.x);
coneNUScaleXSlider.setMajorTickSpacing(1.0f);
coneNUScaleXSlider.setPaintTicks(true);
coneNUScaleXSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneNUScale.x = (double) e.getValue();
useUniformScale = false;
if (useCompoundTransform) {
updateUsingCompoundTransform();
} else {
setConeNUScale();
}
}
});
panel.add(coneNUScaleXSlider);
FloatLabelJSlider coneNUScaleYSlider = new FloatLabelJSlider("Y: ",
0.1f, 0.0f, 3.0f, (float) coneNUScale.y);
coneNUScaleYSlider.setMajorTickSpacing(1.0f);
coneNUScaleYSlider.setPaintTicks(true);
coneNUScaleYSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneNUScale.y = (double) e.getValue();
useUniformScale = false;
if (useCompoundTransform) {
updateUsingCompoundTransform();
} else {
setConeNUScale();
}
}
});
panel.add(coneNUScaleYSlider);
FloatLabelJSlider coneNUScaleZSlider = new FloatLabelJSlider("Z: ",
0.1f, 0.0f, 3.0f, (float) coneNUScale.z);
coneNUScaleZSlider.setMajorTickSpacing(1.0f);
coneNUScaleZSlider.setPaintTicks(true);
coneNUScaleZSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneNUScale.z = (double) e.getValue();
useUniformScale = false;
if (useCompoundTransform) {
updateUsingCompoundTransform();
} else {
setConeNUScale();
}
}
});
panel.add(coneNUScaleZSlider);
return panel;
}
JPanel rotationPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(0, 1));
panel.add(new LeftAlignComponent(new JLabel("Rotation Axis")));
FloatLabelJSlider coneRotateAxisXSlider = new FloatLabelJSlider("X: ",
0.01f, -1.0f, 1.0f, (float) coneRotateAxis.x);
coneRotateAxisXSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneRotateAxis.x = e.getValue();
normalizeConeRotateAxis();
updateConeAxisAngle();
if (useCompoundTransform) {
updateUsingCompoundTransform();
} else {
setConeRotation();
}
rotAxis.setRotationAxis(coneRotateAxis);
updateConeRotateNormalizedLabels();
}
});
panel.add(coneRotateAxisXSlider);
FloatLabelJSlider coneRotateAxisYSlider = new FloatLabelJSlider("Y: ",
0.01f, -1.0f, 1.0f, (float) coneRotateAxis.y);
coneRotateAxisYSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneRotateAxis.y = e.getValue();
normalizeConeRotateAxis();
updateConeAxisAngle();
if (useCompoundTransform) {
updateUsingCompoundTransform();
} else {
setConeRotation();
}
rotAxis.setRotationAxis(coneRotateAxis);
updateConeRotateNormalizedLabels();
}
});
panel.add(coneRotateAxisYSlider);
FloatLabelJSlider coneRotateAxisZSlider = new FloatLabelJSlider("Z: ",
0.01f, -1.0f, 1.0f, (float) coneRotateAxis.y);
coneRotateAxisZSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneRotateAxis.z = e.getValue();
normalizeConeRotateAxis();
updateConeAxisAngle();
if (useCompoundTransform) {
updateUsingCompoundTransform();
} else {
setConeRotation();
}
rotAxis.setRotationAxis(coneRotateAxis);
updateConeRotateNormalizedLabels();
}
});
panel.add(coneRotateAxisZSlider);
JLabel normalizedLabel = new JLabel("Normalized Rotation Axis");
panel.add(new LeftAlignComponent(normalizedLabel));
;
coneRotateNAxisXLabel = new JLabel("X: 1.000");
panel.add(new LeftAlignComponent(coneRotateNAxisXLabel));
coneRotateNAxisYLabel = new JLabel("Y: 0.000");
panel.add(new LeftAlignComponent(coneRotateNAxisYLabel));
coneRotateNAxisZLabel = new JLabel("Z: 0.000");
panel.add(new LeftAlignComponent(coneRotateNAxisZLabel));
normalizeConeRotateAxis();
updateConeRotateNormalizedLabels();
FloatLabelJSlider coneRotateAxisAngleSlider = new FloatLabelJSlider(
"Angle: ", 1.0f, -180.0f, 180.0f, (float) coneRotateAngle);
coneRotateAxisAngleSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneRotateAngle = e.getValue();
updateConeAxisAngle();
if (useCompoundTransform) {
updateUsingCompoundTransform();
} else {
setConeRotation();
}
}
});
panel.add(coneRotateAxisAngleSlider);
return panel;
}
Box refPtPanel() {
Box panel = new Box(BoxLayout.Y_AXIS);
panel.add(new LeftAlignComponent(new JLabel(
"Reference Point Coordinates")));
// X Ref Pt
FloatLabelJSlider coneRefPtXSlider = new FloatLabelJSlider("X", 0.1f,
-2.0f, 2.0f, coneRefPt.x);
coneRefPtXSlider.setMajorTickSpacing(1.0f);
coneRefPtXSlider.setPaintTicks(true);
coneRefPtXSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneRefPt.x = e.getValue();
useCompoundTransform = true;
updateUsingCompoundTransform();
rotAxis.setRefPt(coneRefPt);
}
});
panel.add(coneRefPtXSlider);
// Y Ref Pt
FloatLabelJSlider coneRefPtYSlider = new FloatLabelJSlider("Y", 0.1f,
-2.0f, 2.0f, coneRefPt.y);
coneRefPtYSlider.setMajorTickSpacing(1.0f);
coneRefPtYSlider.setPaintTicks(true);
coneRefPtYSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneRefPt.y = e.getValue();
useCompoundTransform = true;
updateUsingCompoundTransform();
rotAxis.setRefPt(coneRefPt);
}
});
panel.add(coneRefPtYSlider);
// Z Ref Pt
FloatLabelJSlider coneRefPtZSlider = new FloatLabelJSlider("Z", 0.1f,
-2.0f, 2.0f, coneRefPt.z);
coneRefPtZSlider.setMajorTickSpacing(1.0f);
coneRefPtZSlider.setPaintTicks(true);
coneRefPtZSlider.addFloatListener(new FloatListener() {
public void floatChanged(FloatEvent e) {
coneRefPt.z = e.getValue();
useCompoundTransform = true;
updateUsingCompoundTransform();
rotAxis.setRefPt(coneRefPt);
}
});
panel.add(coneRefPtZSlider);
return panel;
}
JPanel configPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(0, 1));
panel.add(new JLabel("Display annotation:"));
// create the check boxes
rotAxisCheckBox = new JCheckBox(rotAxisString);
rotAxisCheckBox.setSelected(showRotAxis);
rotAxisCheckBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
showRotAxis = ((JCheckBox) source).isSelected();
if (showRotAxis) {
rotAxis.setWhichChild(Switch.CHILD_ALL);
} else {
rotAxis.setWhichChild(Switch.CHILD_NONE);
}
}
});
panel.add(rotAxisCheckBox);
coordSysCheckBox = new JCheckBox(coordSysString);
coordSysCheckBox.setSelected(showCoordSys);
coordSysCheckBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
showCoordSys = ((JCheckBox) source).isSelected();
if (showCoordSys) {
coordSys.setWhichChild(Switch.CHILD_ALL);
} else {
coordSys.setWhichChild(Switch.CHILD_NONE);
}
}
});
panel.add(coordSysCheckBox);
if (isApplication) {
JButton snapButton = new JButton(snapImageString);
snapButton.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);
nf.setMaximumFractionDigits(0);
offScreenCanvas.snapImageFile(outFileBase
+ nf.format(outFileSeq++), dim.width, dim.height);
nf.setMinimumIntegerDigits(0);
}
});
panel.add(snapButton);
}
return panel;
}
public void destroy() {
u.removeAllLocales();
}
// The following allows TransformExplorer to be run as an application
// as well as an applet
//
public static void main(String[] args) {
float initOffScreenScale = 2.5f;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-s")) {
if (args.length >= (i + 1)) {
initOffScreenScale = Float.parseFloat(args[i + 1]);
i++;
}
}
}
new MainFrame(new TransformExplorer(true, initOffScreenScale), 950, 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);
}
}
class CoordSys extends Switch {
// Temporaries that are reused
Transform3D tmpTrans = new Transform3D();
Vector3f tmpVector = new Vector3f();
AxisAngle4f tmpAxisAngle = new AxisAngle4f();
// colors for use in the shapes
Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
Color3f grey = new Color3f(0.3f, 0.3f, 0.3f);
Color3f white = new Color3f(1.0f, 1.0f, 1.0f);
// geometric constants
Point3f origin = new Point3f();
Vector3f yAxis = new Vector3f(0.0f, 1.0f, 0.0f);
CoordSys(float axisLength) {
super(Switch.CHILD_ALL);
float coordSysLength = axisLength;
float labelOffset = axisLength / 20.0f;
float axisRadius = axisLength / 500.0f;
float arrowRadius = axisLength / 125.0f;
float arrowHeight = axisLength / 50.0f;
float tickRadius = axisLength / 125.0f;
float tickHeight = axisLength / 250.0f;
// Set the Switch to allow changes
setCapability(Switch.ALLOW_SWITCH_READ);
setCapability(Switch.ALLOW_SWITCH_WRITE);
// Set up an appearance to make the Axis have
// grey ambient, black emmissive, grey diffuse and grey specular
// coloring.
//Material material = new Material(grey, black, grey, white, 64);
Material material = new Material(white, black, white, white, 64);
Appearance appearance = new Appearance();
appearance.setMaterial(material);
// Create a shared group to hold one axis of the coord sys
SharedGroup coordAxisSG = new SharedGroup();
// create a cylinder for the central line of the axis
Cylinder cylinder = new Cylinder(axisRadius, coordSysLength, appearance);
// cylinder goes from -coordSysLength/2 to coordSysLength in y
coordAxisSG.addChild(cylinder);
// create the shared arrowhead
Cone arrowHead = new Cone(arrowRadius, arrowHeight, appearance);
SharedGroup arrowHeadSG = new SharedGroup();
arrowHeadSG.addChild(arrowHead);
// Create a TransformGroup to move the arrowhead to the top of the
// axis
// The arrowhead goes from -arrowHeight/2 to arrowHeight/2 in y.
// Put it at the top of the axis, coordSysLength / 2
tmpVector.set(0.0f, coordSysLength / 2 + arrowHeight / 2, 0.0f);
tmpTrans.set(tmpVector);
TransformGroup topTG = new TransformGroup();
topTG.setTransform(tmpTrans);
topTG.addChild(new Link(arrowHeadSG));
coordAxisSG.addChild(topTG);
// create the minus arrowhead
// Create a TransformGroup to turn the cone upside down:
// Rotate 180 degrees around Z axis
tmpAxisAngle.set(0.0f, 0.0f, 1.0f, (float) Math.toRadians(180));
tmpTrans.set(tmpAxisAngle);
// Put the arrowhead at the bottom of the axis
tmpVector.set(0.0f, -coordSysLength / 2 - arrowHeight / 2, 0.0f);
tmpTrans.setTranslation(tmpVector);
TransformGroup bottomTG = new TransformGroup();
bottomTG.setTransform(tmpTrans);
bottomTG.addChild(new Link(arrowHeadSG));
coordAxisSG.addChild(bottomTG);
// Now add "ticks" at 1, 2, 3, etc.
// create a shared group for the tick
Cylinder tick = new Cylinder(tickRadius, tickHeight, appearance);
SharedGroup tickSG = new SharedGroup();
tickSG.addChild(tick);
// transform each instance and add it to the coord axis group
int maxTick = (int) (coordSysLength / 2);
int minTick = -maxTick;
for (int i = minTick; i <= maxTick; i++) {
if (i == 0)
continue; // no tick at 0
// use a TransformGroup to offset to the tick location
TransformGroup tickTG = new TransformGroup();
tmpVector.set(0.0f, (float) i, 0.0f);
tmpTrans.set(tmpVector);
tickTG.setTransform(tmpTrans);
// then link to an instance of the Tick shared group
tickTG.addChild(new Link(tickSG));
// add the TransformGroup to the coord axis
coordAxisSG.addChild(tickTG);
}
// add a Link to the axis SharedGroup to the coordSys
addChild(new Link(coordAxisSG)); // Y axis
// Create TransformGroups for the X and Z axes
TransformGroup xAxisTG = new TransformGroup();
// rotate 90 degrees around Z axis
tmpAxisAngle.set(0.0f, 0.0f, 1.0f, (float) Math.toRadians(90));
tmpTrans.set(tmpAxisAngle);
xAxisTG.setTransform(tmpTrans);
xAxisTG.addChild(new Link(coordAxisSG));
addChild(xAxisTG); // X axis
TransformGroup zAxisTG = new TransformGroup();
// rotate 90 degrees around X axis
tmpAxisAngle.set(1.0f, 0.0f, 0.0f, (float) Math.toRadians(90));
tmpTrans.set(tmpAxisAngle);
zAxisTG.setTransform(tmpTrans);
zAxisTG.addChild(new Link(coordAxisSG));
addChild(zAxisTG); // Z axis
// Add the labels. First we need a Font3D for the Text3Ds
// select the default font, plain style, 0.5 tall. Use null for
// the extrusion so we get "flat" text since we will be putting it
// into an oriented Shape3D
Font3D f3d = new Font3D(new Font("Default", Font.PLAIN, 1), null);
// set up the +X label
Text3D plusXText = new Text3D(f3d, "+X", origin, Text3D.ALIGN_CENTER,
Text3D.PATH_RIGHT);
// orient around the local origin
OrientedShape3D plusXTextShape = new OrientedShape3D(plusXText,
appearance, OrientedShape3D.ROTATE_ABOUT_POINT, origin);
// transform to scale down to 0.15 in height, locate at end of axis
TransformGroup plusXTG = new TransformGroup();
tmpVector.set(coordSysLength / 2 + labelOffset, 0.0f, 0.0f);
tmpTrans.set(0.15f, tmpVector);
plusXTG.setTransform(tmpTrans);
plusXTG.addChild(plusXTextShape);
addChild(plusXTG);
// set up the -X label
Text3D minusXText = new Text3D(f3d, "-X", origin, Text3D.ALIGN_CENTER,
Text3D.PATH_RIGHT);
// orient around the local origin
OrientedShape3D minusXTextShape = new OrientedShape3D(minusXText,
appearance, OrientedShape3D.ROTATE_ABOUT_POINT, origin);
// transform to scale down to 0.15 in height, locate at end of axis
TransformGroup minusXTG = new TransformGroup();
tmpVector.set(-coordSysLength / 2 - labelOffset, 0.0f, 0.0f);
tmpTrans.set(0.15f, tmpVector);
minusXTG.setTransform(tmpTrans);
minusXTG.addChild(minusXTextShape);
addChild(minusXTG);
// set up the +Y label
Text3D plusYText = new Text3D(f3d, "+Y", origin, Text3D.ALIGN_CENTER,
Text3D.PATH_RIGHT);
// orient around the local origin
OrientedShape3D plusYTextShape = new OrientedShape3D(plusYText,
appearance, OrientedShape3D.ROTATE_ABOUT_POINT, origin);
// transform to scale down to 0.15 in height, locate at end of axis
TransformGroup plusYTG = new TransformGroup();
tmpVector.set(0.0f, coordSysLength / 2 + labelOffset, 0.0f);
tmpTrans.set(0.15f, tmpVector);
plusYTG.setTransform(tmpTrans);
plusYTG.addChild(plusYTextShape);
addChild(plusYTG);
// set up the -Y label
Text3D minusYText = new Text3D(f3d, "-Y", origin, Text3D.ALIGN_CENTER,
Text3D.PATH_RIGHT);
// orient around the local origin
OrientedShape3D minusYTextShape = new OrientedShape3D(minusYText,
appearance, OrientedShape3D.ROTATE_ABOUT_POINT, origin);
// transform to scale down to 0.15 in height, locate at end of axis
TransformGroup minusYTG = new TransformGroup();
tmpVector.set(0.0f, -coordSysLength / 2 - labelOffset, 0.0f);
tmpTrans.set(0.15f, tmpVector);
minusYTG.setTransform(tmpTrans);
minusYTG.addChild(minusYTextShape);
addChild(minusYTG);
// set up the +Z label
Text3D plusZText = new Text3D(f3d, "+Z", origin, Text3D.ALIGN_CENTER,
Text3D.PATH_RIGHT);
// orient around the local origin
OrientedShape3D plusZTextShape = new OrientedShape3D(plusZText,
appearance, OrientedShape3D.ROTATE_ABOUT_POINT, origin);
// transform to scale down to 0.15 in height, locate at end of axis
TransformGroup plusZTG = new TransformGroup();
tmpVector.set(0.0f, 0.0f, coordSysLength / 2 + labelOffset);
tmpTrans.set(0.15f, tmpVector);
plusZTG.setTransform(tmpTrans);
plusZTG.addChild(plusZTextShape);
addChild(plusZTG);
// set up the -Z label
Text3D minusZText = new Text3D(f3d, "-Z", origin, Text3D.ALIGN_CENTER,
Text3D.PATH_RIGHT);
// orient around the local origin
OrientedShape3D minusZTextShape = new OrientedShape3D(minusZText,
appearance, OrientedShape3D.ROTATE_ABOUT_POINT, origin);
// transform to scale down to 0.15 in height, locate at end of axis
TransformGroup minusZTG = new TransformGroup();
tmpVector.set(0.0f, 0.0f, -coordSysLength / 2 - labelOffset);
tmpTrans.set(0.15f, tmpVector);
minusZTG.setTransform(tmpTrans);
minusZTG.addChild(minusZTextShape);
addChild(minusZTG);
}
}
class LeftAlignComponent extends JPanel {
LeftAlignComponent(Component c) {
setLayout(new BorderLayout());
add(c, BorderLayout.WEST);
}
}
class RotAxis extends Switch implements Java3DExplorerConstants {
// axis to align with
Vector3f rotAxis = new Vector3f(1.0f, 0.0f, 0.0f);
// offset to ref point
Vector3f refPt = new Vector3f(0.0f, 0.0f, 0.0f);
TransformGroup axisTG; // the transform group used to align the axis
// Temporaries that are reused
Transform3D tmpTrans = new Transform3D();
Vector3f tmpVector = new Vector3f();
AxisAngle4f tmpAxisAngle = new AxisAngle4f();
// geometric constants
Point3f origin = new Point3f();
Vector3f yAxis = new Vector3f(0.0f, 1.0f, 0.0f);
RotAxis(float axisLength) {
super(Switch.CHILD_NONE);
setCapability(Switch.ALLOW_SWITCH_READ);
setCapability(Switch.ALLOW_SWITCH_WRITE);
// set up the proportions for the arrow
float axisRadius = axisLength / 120.0f;
float arrowRadius = axisLength / 50.0f;
float arrowHeight = axisLength / 30.0f;
// create the TransformGroup which will be used to orient the axis
axisTG = new TransformGroup();
axisTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
axisTG.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
addChild(axisTG);
// Set up an appearance to make the Axis have
// blue ambient, black emmissive, blue diffuse and white specular
// coloring.
Material material = new Material(blue, black, blue, white, 64);
Appearance appearance = new Appearance();
appearance.setMaterial(material);
// create a cylinder for the central line of the axis
Cylinder cylinder = new Cylinder(axisRadius, axisLength, appearance);
// cylinder goes from -length/2 to length/2 in y
axisTG.addChild(cylinder);
// create a SharedGroup for the arrowHead
Cone arrowHead = new Cone(arrowRadius, arrowHeight, appearance);
SharedGroup arrowHeadSG = new SharedGroup();
arrowHeadSG.addChild(arrowHead);
// Create a TransformGroup to move the cone to the top of the
// cylinder
tmpVector.set(0.0f, axisLength/2 + arrowHeight / 2, 0.0f);
tmpTrans.set(tmpVector);
TransformGroup topTG = new TransformGroup();
topTG.setTransform(tmpTrans);
topTG.addChild(new Link(arrowHeadSG));
axisTG.addChild(topTG);
// create the bottom of the arrow
// Create a TransformGroup to move the cone to the bottom of the
// axis so that its pushes into the bottom of the cylinder
tmpVector.set(0.0f, -(axisLength / 2), 0.0f);
tmpTrans.set(tmpVector);
TransformGroup bottomTG = new TransformGroup();
bottomTG.setTransform(tmpTrans);
bottomTG.addChild(new Link(arrowHeadSG));
axisTG.addChild(bottomTG);
updateAxisTransform();
}
public void setRotationAxis(Vector3f setRotAxis) {
rotAxis.set(setRotAxis);
float magSquared = rotAxis.lengthSquared();
if (magSquared > 0.0001) {
rotAxis.scale((float)(1.0 / Math.sqrt(magSquared)));
} else {
rotAxis.set(1.0f, 0.0f, 0.0f);
}
updateAxisTransform();
}
public void setRefPt(Vector3f setRefPt) {
refPt.set(setRefPt);
updateAxisTransform();
}
// set the transform on the axis so that it aligns with the rotation
// axis and goes through the reference point
private void updateAxisTransform() {
// We need to rotate the axis, which is defined along the y-axis,
// to the direction indicated by the rotAxis.
// We can do this using a neat trick. To transform a vector to align
// with another vector (assuming both vectors have unit length), take
// the cross product the the vectors. The direction of the cross
// product is the axis, and the length of the cross product is the
// the sine of the angle, so the inverse sine of the length gives
// us the angle
tmpVector.cross(yAxis, rotAxis);
float angle = (float)Math.asin(tmpVector.length());
tmpAxisAngle.set(tmpVector, angle);
tmpTrans.set(tmpAxisAngle);
tmpTrans.setTranslation(refPt);
axisTG.setTransform(tmpTrans);
}
}
Related examples in the same category