I sent out a program yesterday to demonstrate a null pointer when detaching and reattaching groups quickly. Well, it responds to keystrokes and I put a keylistener on the canvas and it works if you click on the canvas (which I was doing when I was running it since there is a rotate and room behavior enabled and I liked poking at it) but if you just start it up and hit keys it ignores you. To fix this I added a keylistener to the applet as well. And since I can't avoid playing I also set it so that 9 on the neatest rotation sets I played with are mapped to the keys qweasdzxc. Also you can change the distance of rotation using the 8/2 and 4/6 on the numeric keypad. It looks pretty neat with the number of cubes set really high. (The number of cubes being the one thing that has to be set at load time. I like playing but that was too much work for a playtime project.) =) So this is the same program but improved a bit. Will Holcomb
import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.media.j3d.*; import javax.vecmath.*; import com.sun.j3d.utils.universe.*; import com.sun.j3d.utils.behaviors.mouse.*; import com.sun.j3d.utils.image.TextureLoader; import com.sun.j3d.utils.applet.MainFrame; import com.sun.j3d.utils.geometry.ColorCube; import com.sun.j3d.utils.geometry.*; public class DetachTest extends JApplet implements KeyListener { int speed; static float defaultLength = 1; static double defaultCenterOffset = 3; static int defaultNumSections = 4; static int defaultSpeed = 3000; Alpha rotationAlpha; BranchGroup[] cubeGroup; TransformGroup[] offset; RotationInterpolator[] rotator; TransformGroup[] initialPosition; Group holder; final int UP = 1; final int DOWN = 2; int direction = 0; int index = 0; boolean paused = false; Vector3d offsetVector = new Vector3d(); public DetachTest() { this(defaultLength, defaultCenterOffset, defaultNumSections, defaultSpeed); } public DetachTest(float length) { this(length, defaultCenterOffset, defaultNumSections, defaultSpeed); } public DetachTest(float length, double centerOffset) { this(length, centerOffset, defaultNumSections, defaultSpeed); } public DetachTest(float length, double centerOffset, int numSections) { this(length, centerOffset, numSections, defaultSpeed); } public DetachTest(float length, double centerOffset, int numSections, int speed) { this.speed = speed; VirtualUniverse universe = new VirtualUniverse(); Locale locale = new Locale(universe); BranchGroup head = new BranchGroup(); holder = new Group(); holder.setCapability(Group.ALLOW_CHILDREN_WRITE); holder.setCapability(Group.ALLOW_CHILDREN_EXTEND); BoundingSphere bounds = new BoundingSphere(new Point3d(), Double.MAX_VALUE); Background background = new Background(new Color3f(29f / 255f, 43f / 255f, 153f / 255f)); background.setApplicationBounds(bounds); head.addChild(background); DirectionalLight light = new DirectionalLight (new Color3f(100f / 255f, 100f / 255f, 100f / 255f), new Vector3f(0f, 0f, -1f)); light.setInfluencingBounds(bounds); head.addChild(light); TransformGroup rotationTransformation = new TransformGroup(); rotationTransformation.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); rotationTransformation.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); head.addChild(rotationTransformation); MouseRotate rotateBehavior = new MouseRotate(rotationTransformation); rotateBehavior.setSchedulingBounds(bounds); rotationTransformation.addChild(rotateBehavior); numSections = Math.max(numSections, 1); offsetVector = new Vector3d(centerOffset, 0, 0); Transform3D rotation; Transform3D translation = new Transform3D(); translation.setTranslation(offsetVector); initialPosition = new TransformGroup[numSections]; TransformGroup[] interpolator = new TransformGroup[numSections]; offset = new TransformGroup[numSections]; cubeGroup = new BranchGroup[numSections]; rotationAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE, 0, 0, speed, 0, 0, 0, 0, 0); Transform3D axis = new Transform3D(); rotator = new RotationInterpolator[numSections]; for(index = 0; index < numSections; index++) { axis.rotX(index * Math.PI * 2 / numSections); rotation = new Transform3D(); rotation.rotZ(index * Math.PI * 2 / numSections); rotation.mul(translation); initialPosition[index] = new TransformGroup(rotation); initialPosition[index].setCapability(TransformGroup.ALLOW_TRANSFORM_READ); initialPosition[index].setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); rotationTransformation.addChild(initialPosition[index]); interpolator[index] = new TransformGroup(); interpolator[index].setCapability(TransformGroup.ALLOW_TRANSFORM_READ); interpolator[index].setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); initialPosition[index].addChild(interpolator[index]); offset[index] = new TransformGroup(translation); offset[index].setCapability(Group.ALLOW_CHILDREN_WRITE); offset[index].setCapability(Group.ALLOW_CHILDREN_EXTEND); offset[index].setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); interpolator[index].addChild(offset[index]); cubeGroup[index] = new BranchGroup(); cubeGroup[index].setCapability(BranchGroup.ALLOW_DETACH); offset[index].addChild(cubeGroup[index]); cubeGroup[index].addChild(new ColorCube(length)); rotator[index] = new RotationInterpolator(rotationAlpha, interpolator[index], axis, 0, (float)Math.PI * 2f); rotator[index].setSchedulingBounds(bounds); interpolator[index].addChild(rotator[index]); } index = numSections - 1; direction = DOWN; Canvas3D canvas = new Canvas3D(SimpleUniverse.getPreferredConfiguration()); View view = new View(); view.setPhysicalBody(new PhysicalBody()); view.setPhysicalEnvironment(new PhysicalEnvironment()); view.setBackClipDistance(5000); view.addCanvas3D(canvas); ViewPlatform viewPlatform = new ViewPlatform(); view.attachViewPlatform(viewPlatform); translation.setTranslation(new Vector3d(0, 0, (length + centerOffset) * 7)); TransformGroup viewTransformation = new TransformGroup(translation); viewTransformation.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); viewTransformation.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); head.addChild(viewTransformation); viewTransformation.addChild(viewPlatform); MouseZoom zoomBehavior = new MouseZoom(viewTransformation); zoomBehavior.setSchedulingBounds(bounds); viewTransformation.addChild(zoomBehavior); getContentPane().add(canvas); canvas.addKeyListener(this); addKeyListener(this); head.compile(); locale.addBranchGraph(head); } public void keyPressed(KeyEvent e) { if(e.getKeyCode() == KeyEvent.VK_UP) { speed -= 10; rotationAlpha.setIncreasingAlphaDuration(speed); } else if(e.getKeyCode() == KeyEvent.VK_DOWN) { speed += 10; rotationAlpha.setIncreasingAlphaDuration(speed); } else if(e.getKeyCode() == KeyEvent.VK_PAGE_UP) { speed -= 500; rotationAlpha.setIncreasingAlphaDuration(speed); } else if(e.getKeyCode() == KeyEvent.VK_PAGE_DOWN) { speed += 500; rotationAlpha.setIncreasingAlphaDuration(speed); } else if(e.getKeyCode() == KeyEvent.VK_P) { paused = !paused; if(paused) { rotationAlpha.setIncreasingAlphaDuration(0); } else { rotationAlpha.setIncreasingAlphaDuration(speed); } } else if(e.getKeyCode() == KeyEvent.VK_Q) { Transform3D axis = new Transform3D(); for(int i = 0; i < rotator.length; i++) { if(i % 2 == 0) { axis.rotX(Math.PI / 2); } else { axis.rotX(-Math.PI / 2); } rotator[i].setAxisOfRotation(axis); } } else if(e.getKeyCode() == KeyEvent.VK_W) { Transform3D axis = new Transform3D(); for(int i = 0; i < rotator.length; i++) { axis.rotX(i * Math.PI * 2 / rotator.length); rotator[i].setAxisOfRotation(axis); } } else if(e.getKeyCode() == KeyEvent.VK_E) { Transform3D axis = new Transform3D(); for(int i = 0; i < rotator.length; i++) { if(i < rotator.length / 2) { axis.rotX(i * Math.PI * 2 / rotator.length); } else { axis.rotX(-i * Math.PI * 2 / rotator.length); } rotator[i].setAxisOfRotation(axis); } } else if(e.getKeyCode() == KeyEvent.VK_A) { Transform3D axis = new Transform3D(); axis.rotX(Math.PI / 2); for(int i = 0; i < rotator.length; i++) { rotator[i].setAxisOfRotation(axis); } } else if(e.getKeyCode() == KeyEvent.VK_S) { Transform3D axis = new Transform3D(); axis.rotY(Math.PI / 2); for(int i = 0; i < rotator.length; i++) { rotator[i].setAxisOfRotation(axis); } } else if(e.getKeyCode() == KeyEvent.VK_D) { Transform3D axis = new Transform3D(); axis.rotZ(Math.PI / 2); for(int i = 0; i < rotator.length; i++) { rotator[i].setAxisOfRotation(axis); } } else if(e.getKeyCode() == KeyEvent.VK_Z) { Transform3D axis = new Transform3D(); for(int i = 0; i < rotator.length; i++) { axis.setEuler(new Vector3d(Math.random() * Math.PI * 2, Math.random() * Math.PI * 2, Math.random() * Math.PI * 2)); rotator[i].setAxisOfRotation(axis); } } else if(e.getKeyCode() == KeyEvent.VK_X) { Transform3D axis = new Transform3D(); for(int i = 0; i < rotator.length; i++) { axis.setEuler(new Vector3d(i * Math.PI * 2 / rotator.length, i * Math.PI * 2 / rotator.length, i * Math.PI * 2 / rotator.length)); rotator[i].setAxisOfRotation(axis); } } else if(e.getKeyCode() == KeyEvent.VK_C) { Transform3D axis = new Transform3D(); for(int i = 0; i < rotator.length; i++) { axis.rotZ(i * Math.PI * 2 / rotator.length); rotator[i].setAxisOfRotation(axis); } } else if(e.getKeyCode() == KeyEvent.VK_NUMPAD8) { Transform3D translation = new Transform3D(); Transform3D position = new Transform3D(); offsetVector.x += .5; translation.setTranslation(offsetVector); for(int i = 0; i < offset.length; i++) { initialPosition[i].getTransform(position); position.setTranslation(new Vector3d()); position.mul(translation); initialPosition[i].setTransform(position); offset[i].setTransform(translation); } } else if(e.getKeyCode() == KeyEvent.VK_NUMPAD2) { Transform3D translation = new Transform3D(); Transform3D position = new Transform3D(); offsetVector.x -= .5; translation.setTranslation(offsetVector); for(int i = 0; i < offset.length; i++) { initialPosition[i].getTransform(position); position.setTranslation(new Vector3d()); position.mul(translation); initialPosition[i].setTransform(position); offset[i].setTransform(translation); } } else if(e.getKeyCode() == KeyEvent.VK_NUMPAD6) { Transform3D translation = new Transform3D(); Transform3D position = new Transform3D(); offsetVector.y += .5; translation.setTranslation(offsetVector); for(int i = 0; i < offset.length; i++) { initialPosition[i].getTransform(position); position.setTranslation(new Vector3d()); position.mul(translation); initialPosition[i].setTransform(position); offset[i].setTransform(translation); } } else if(e.getKeyCode() == KeyEvent.VK_NUMPAD4) { Transform3D translation = new Transform3D(); Transform3D position = new Transform3D(); offsetVector.y -= .5; translation.setTranslation(offsetVector); for(int i = 0; i < offset.length; i++) { initialPosition[i].getTransform(position); position.setTranslation(new Vector3d()); position.mul(translation); initialPosition[i].setTransform(position); offset[i].setTransform(translation); } } else if(e.getKeyCode() == KeyEvent.VK_R) { if(index < 0) { index = 0; direction = UP; } else if(index >= cubeGroup.length) { index = cubeGroup.length - 1; direction = DOWN; } if(direction == DOWN) { holder.moveTo(cubeGroup[index]); index--; } else if(direction == UP) { offset[index].moveTo(cubeGroup[index]); index++; } return; } } public void keyReleased(KeyEvent e) {} public void keyTyped(KeyEvent e) {} public static void main(String[] args) { float length = DetachTest.defaultLength; double offset = DetachTest.defaultCenterOffset; int numSections = DetachTest.defaultNumSections; int speed = DetachTest.defaultSpeed; boolean error = false; for(int i = 0; i < args.length; i++) { if(args[i].length() > 2) { if(args[i].toLowerCase().startsWith("-l")) { try { length = Float.parseFloat(args[i].substring(2)); } catch(NumberFormatException e) { System.err.println("\"" + args[i].substring(2) + "\" is not a valid float."); error = true; } } else if(args[i].toLowerCase().startsWith("-o")) { try { offset = Double.parseDouble(args[i].substring(2)); } catch(NumberFormatException e) { System.err.println("\"" + args[i].substring(2) + "\" is not a valid double."); error = true; } } else if(args[i].toLowerCase().startsWith("-n")) { try { numSections = Integer.parseInt(args[i].substring(2)); } catch(NumberFormatException e) { System.err.println("\"" + args[i].substring(2) + "\" is not a valid integer."); error = true; } } else if(args[i].toLowerCase().startsWith("-s")) { try { speed = Integer.parseInt(args[i].substring(2)); } catch(NumberFormatException e) { System.err.println("\"" + args[i].substring(2) + "\" is not a valid integer."); error = true; } } else { System.err.println("Unknown option \"" + args[i].substring(0, 2) + "\""); error = true; } } else { if(args[i].toLowerCase().startsWith("-h")) { error = true; } else { System.err.println("Unknown option \"" + args[i] + "\""); error = true; } } } if(error) { System.out.println("Usage:"); System.out.println(" DetachTest -l(float) -h(double) -n(integer) -s(integer) -h"); System.out.println(" -l => length => Sets the size of the color cubes"); System.out.println(" -o => offset => Sets rotation distance"); System.out.println(" -n => number => Sets the number of cubes"); System.out.println(" -s => speed => Sets the delay in milliseconds"); System.out.println(" -h => help => Prints this message"); System.out.println(""); System.out.println(" Once the program is started pressing the up and down keys will"); System.out.println(" increase the delay by 10 millisecond and pgup and pgdown will"); System.out.println(" increase it by 500. Also the p key will toggle it between its"); System.out.println(" current state and 0. Some setting like -n50 -o0 are pretty."); System.out.println(" (Well, if your machine can get better than .3fps.) =)"); System.out.println(""); System.out.println(" The purpose of this program is to illustrate some bugs in the java"); System.out.println(" 3d libraries. Whenever the r key is pressed one of the cubes will"); System.out.println(" be removed from the scene graph and attached to a group not in the"); System.out.println(" scene using the Group.moveTo(BranchGroup) method. In the 1.1"); System.out.println(" implementation the cubes do not disappear when removed, or at least"); System.out.println(" not until they are begun to be put back in. In the 1.2 implementation"); System.out.println(" they disappear, but if you add and remove them too quickly you get"); System.out.println(" a null pointer exception and rendering stops."); } else { new MainFrame(new DetachTest(length, offset, numSections, speed), 400, 300); } } }