I am not sure if this bug has been documented or not. It occurs when doing
repeated calls to Group.moveTo(BranchGroup). It is some sort of race
condition (I use the term loosely becasue I only kinda know what it
maens) =) becasue if you do it slowly then it works fine. If you do it
quickly then you get a null pointer exception.

This little test program illustrates it. It is interesting becasue it
shows one bug with the 1.1 stuff and another with the 1.2. It looks cute
though in either one. Instructions may be had by running it with -h as an
argument.

All it does is create a graph with a bunch of color cubes attached below
rotation interpolators and whenever r is hit it moves one of the cubes
(via a branch group between the cube and the behavior) to a node not in
the scene graph. If all the nodes are out then it starts putting them
back. Just for kicks the cubes are spaced around a circle and also the
axis of rotation travels in a similar circle at right angles to the
first. It looks kinda cute if you tell it to do 50 cubes or so (though I
am running on a dual 450 with a NVidia GeForce so I can't speak on how
quickly it runs on other people's machines. =)

I just wrote this today and I think it works ok. It might break though. =)

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;
    Group holder;
    
    final int UP = 1;
    final int DOWN = 2;
    int direction = 0;
    int index = 0;
    boolean paused = false;

    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_READ);
        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);
        Transform3D rotation;
        Transform3D translation = new Transform3D();
        translation.setTranslation(new Vector3d(centerOffset, 0, 0));
        TransformGroup[] 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();
        RotationInterpolator[] 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);
            rotationTransformation.addChild(initialPosition[index]);
            interpolator[index] = new TransformGroup();
            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);
            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.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);

        head.compile();
        locale.addBranchGraph(head);
    }

    public void keyPressed(KeyEvent e) {
        int newSpeed = speed;
        if(e.getKeyCode() == KeyEvent.VK_UP) {
            newSpeed = speed -= 10;
        } else if(e.getKeyCode() == KeyEvent.VK_DOWN) {
            newSpeed = speed += 10;
        } else if(e.getKeyCode() == KeyEvent.VK_PAGE_UP) {
            newSpeed = speed -= 500;
        } else if(e.getKeyCode() == KeyEvent.VK_PAGE_DOWN) {
            newSpeed = speed += 500;
        } else if(e.getKeyCode() == KeyEvent.VK_P) {
            paused = !paused;
            if(paused) {
                newSpeed = 0;
            }
        } 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;
        }
        rotationAlpha.setIncreasingAlphaDuration(newSpeed);
    }

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

Reply via email to