Java tutorial
/* * %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); } }