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.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); } }