I have been experiencing some interesting behavior.  I have
several BranchGroups representing individual levels in a
"give 2D objects 3D properties" style application.  I use
LineArrays to represent the 2D objects.

The behavior arises when I zoom in close to one of the objects,
and don't touch it for any extended period of time (around
10 seconds).  When I zoom back out, several of the LineArrays
are non longer being drawn.  I've tried setting

setVisibilityPolicy(View.VISIBILITY_DRAW_ALL);

on the View, but that didn't help.

Any ideas regarding this matter would be greatly appreciated.
I have attached a small program that demonstrates this behavior.

Michael Rutter
import java.awt.*;
import java.awt.event.*;
import javax.swing.JFrame;
import java.util.Vector;

import javax.vecmath.*;
import javax.media.j3d.*;
import com.sun.j3d.utils.geometry.Text2D;
import com.sun.j3d.utils.picking.*;
import com.sun.j3d.utils.universe.SimpleUniverse;

/**
 *  This is a simple class ment to demonstrate an undesired behavior of Java3D.
 *  There are four ways to move the scene graph around by draggging the mouse:
 *  1) Tilt: The first mouse button will cause each individual "Level" to
 *     tilt about the x-axis, along the middle of the level.
 *  2) Pan: CRTL+First mouse button will cause the levels to pan about.
 *  3) Zoom: The second (middle) mouse button will cause the scene to zoom
 *     smaller when dragged up, and zoom larger when dragged down.
 *  4) Spin: The third mouse button will cause each level will spin about the y-axis.
 *
 *  Using these tools, tilt and spin the drawing just for fun.  Then zoom
 *  in upon one of the individual levels (scale should equal 45 or higher),
 *  and let it sit for about ten seconds (moving it about a little is fine).
 *  Finally zoom back out to where it is possible to see all of the levels
 *  (scale ~= 1).  At this point, there will most likely be only one or two
 *  of the seven levels still visible (but all of the text).  This disappearance
 *  of shapes is the undesired behavior.  An interesting bit to note is that
 *  while the LineArrays disappear, the Text objects remain.  The LineArrays
 *  are still there, in the application it is still possible to pick them,
 *  they just aren't being drawn.
 *
 *  The organazation of the scene graph comes decently close to mimicking that
 *  of the project where the problem originally popped up.  What the project does
 *  is complex, hence the complexity of the scene graph.
 */
public class TestBlank extends JFrame implements MouseMotionListener{
        protected static final int NUM_LEVELS = 7;

        // Point I keep around to get good delta values when dragging the mouse.
        protected Point lastMousePos = new Point(0, 0);
        // Transform group used for panning, and for holding the levels.
        protected TransformGroup objScale = null;
        // Outer group used to zoom
        protected TransformGroup scaleGroup = null;
        // Transform3D used for panning
        protected Transform3D t3d;
        // Transform3D used for zooming
        protected Transform3D scaleT3D;
        // The seven individual levels
        protected Block[] levels;

        // Overall scale of the drawing
        protected double scale;
        // Initial scale value based upon the size of the elements
        // put into the scene graph.
        protected double initialScale;
        // Vector3d used to keep track of the drawing's position
        protected Vector3d position;

        // Temp variables I keep around to make panning/scaling more efficient.
        protected Vector3d tempVector3d;
        protected Matrix3d tempMatrix3d;

        public TestBlank() {
                super("Show that the evil shapes disappear");

                Canvas3D canvas = new 
Canvas3D(SimpleUniverse.getPreferredConfiguration());

                // Create a scene and attach it to the virtual universe
                BranchGroup scene = createSceneGraph(canvas);
                SimpleUniverse u = new SimpleUniverse(canvas);

                Background background = new Background(scene);
                background.setColor(1.0f, 1.0f, 1.0f);

                // This will move the ViewPlatform back a bit so the
                // objects in the scene can be viewed.
                u.getViewingPlatform().setNominalViewingTransform();
                u.addBranchGraph(scene);

                PickCanvas pickCanvas = new PickCanvas(canvas, scene);
                pickCanvas.setMode(PickTool.BOUNDS);
                pickCanvas.setTolerance(1.0f);

                // We are looking at ship drawings usually, so we don't like the
                // perspective projection.
                canvas.getView().setProjectionPolicy(View.PARALLEL_PROJECTION);
                // This is an attemp to get Java3D to quit stopping drawing objects
                // that aren't currently displayed.  When a user zooms in close on
                // a drawing, it's possible that Java3D will drop a lot of the objects
                // that are beyond the visual scope.  This wouldn't be a bad thing if
                // Java3D picked them back up once they became visible again, but it
                // doesn't.  This doesn't seem to solve the problem
                canvas.getView().setVisibilityPolicy(View.VISIBILITY_DRAW_ALL);

                this.getContentPane().setLayout(new BorderLayout());
                this.getContentPane().add(canvas, BorderLayout.CENTER);

                this.addWindowListener(new WindowAdapter() {
                        public void windowClosing() {
                                System.exit(0);
                        }
                });
        }

        protected BranchGroup createSceneGraph(Canvas3D canvas) {
                BranchGroup objRoot = new BranchGroup();

                // Set up the group that we're going to be using for zooming.
                scaleT3D   = new Transform3D();
                scaleGroup = new TransformGroup();
                Matrix3f tempRot = new Matrix3f();
                tempRot.rotX(0.0f);
                scaleT3D.set(tempRot, position = new Vector3d(0.0, 0.0, 0.0), scale = 
1.0);
                scaleGroup.setTransform(scaleT3D);
                scaleGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

                // Set up the initial scale so that we can see everything.
                initialScale = Math.min(1.0 / Block.X_DEVIATION, 1.0 / 
(Block.Y_DEVIATION * NUM_LEVELS));
                position.set(0.0, 0.0, 0.0);
                t3d = new Transform3D();
                t3d.set(tempRot, position, initialScale);

                objScale = new TransformGroup();
                objScale.setTransform(t3d);
                objScale.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

                // Create some BS levels and plop them in.
                levels = new Block[NUM_LEVELS];
                for (int index = 0; index < levels.length; index++) {
                        levels[index] = new Block(new Point3d(0.0, 100.0*index, 0.0));
                        objScale.addChild(levels[index]);
                }
                scaleGroup.addChild(objScale);
                objRoot.addChild(scaleGroup);

                // Grabbed from some example code
                BoundingSphere bounds =
                        new BoundingSphere(new Point3d(0.0,0.0,0.0), 10.0);

                // Add a light.
                Color3f lColor = new Color3f(1.0f, 1.0f, 1.0f) ;
                Vector3f lDir  = new Vector3f(0.0f, 0.0f, -1.0f) ;

                DirectionalLight lgt = new DirectionalLight(lColor, lDir) ;
                lgt.setInfluencingBounds(bounds) ;
                objRoot.addChild(lgt);

                // For happy happy mouseDragged events
                canvas.addMouseMotionListener(this);

                // Let's make some optimizations, yea.
                objRoot.compile();

                return objRoot;
        }

        /**
         *  This method is used to PAN, TILT, ZOOM, and SPIN the scene.
         */
        public void mouseDragged(MouseEvent evt) {
                int deltaX = evt.getX() - lastMousePos.x;
                int deltaY = evt.getY() - lastMousePos.y;
                // The delta values are just too big to work with, so knock them
                // down a bit to something more usable.
                double dX = (double)deltaX / 200.0;
                double dY = (double)deltaY / 200.0;

                if ((evt.getModifiers() & InputEvent.BUTTON1_MASK) != 0) {
                        if ((evt.getModifiers() & InputEvent.CTRL_MASK) != 0) {
                                pan(dX, dY);
                        }
                        else {
                                for (int cnt = 0; cnt < levels.length; cnt++) {
                                        levels[cnt].adjustTilt(dY);
                                }
                        }
                }
                else if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0) {
                        zoom(dY);
                }
                else if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0) {
                        for (int cnt = 0; cnt < levels.length; cnt++) {
                                levels[cnt].adjustSpin(dX);
                        }
                }

                lastMousePos.x = evt.getX();
                lastMousePos.y = evt.getY();
        }

        /**
         *  Stay updated on the last position that the mouse was in so that we
         *  can have good delta position values on mouseDragged events.
         */
        public void mouseMoved(MouseEvent evt) {
                lastMousePos.x = evt.getX();
                lastMousePos.y = evt.getY();
        }

        /**
         *  Make the scene bigger or smaller.  Honey, I Shrunk the Drawing!
         */
        protected void zoom(double delta) {
                // This is an arbitrary equation that I came up with.  It
                // seems to work pretty darn good, though.  Nice and smooth.
                scale += delta * ((scale / 2.0) + 1.0);
                scaleT3D.setScale(Math.max(scale, 0.1));
                scaleGroup.setTransform(scaleT3D);
                System.out.println("scale=" + scale);
        } // End of zoom

        /**
         *  Move the scene around.  A little to the left, yeah, that's greeeat.
         */
        protected void pan(double dx, double dy) {
                if (tempVector3d == null) tempVector3d = new Vector3d();
                if (tempMatrix3d == null) tempMatrix3d = new Matrix3d();

                // Adjust the deltas according to the current scale so that we can have
                // similar results for different zoom factors
                dx /= scale;
                dy /= -scale;

                tempVector3d.set(dx, dy, 0.0);
                position.add(tempVector3d);
                tempMatrix3d.setZero();
                tempMatrix3d.rotX(0.0);

                t3d.set(tempMatrix3d, position, initialScale);
                objScale.setTransform(t3d);
        } // End of pan

        /**
         *  A class to simulate an individual level.  In the actual application,
         *  the equivalent class represents a deck on a ship.  Lots of objects.
         */
        public class Block extends BranchGroup {
                // Number of points used for the LineArray
                protected static final int NUM_POINTS = 50;
                // Basically the max X-Value
                protected static final double X_DEVIATION = 1000.0;
                // Basically the max Y-Value
                protected static final double Y_DEVIATION = 100.0;
                // The root TransformGroup
                protected TransformGroup objRoot = null;
                protected Transform3D t3d = null;
                // The inner transform group used so that we can do the nifty tilting.
                protected TransformGroup innerGroup = null;
                // And the corresponding Transform3D
                protected Transform3D innerTrans = null;

                // Again with the persistant temporary variables for efficiency
                protected Matrix3d tempMatrix = null;
                protected Matrix3d rotMatrix = null;

                public Block(Point3d base) {
                        // The points used for the LineArray
                        Point3f points[] = new Point3f[NUM_POINTS];
                        // The colors used for the LineArray
                        Color3b clrs[]   = new Color3b[NUM_POINTS];
                        // The color used to fill the clrs array
                        Color3b color = new Color3b((byte)150, (byte)150, (byte)150);

                        // Create a bunch of random points so we can have a nice 
mish-mash of lines
                        for (int cnt = 0; cnt < NUM_POINTS; cnt++) {
                                clrs[cnt] = color;
                                points[cnt] = new Point3f((float)((Math.random()-0.5) 
* X_DEVIATION + base.x),
                                                          (float)((Math.random()-0.5) 
* Y_DEVIATION + base.y), 0.0f);
                        }

                        objRoot = new TransformGroup();
                        t3d = new Transform3D();
                        tempMatrix = new Matrix3d();
                        rotMatrix = new Matrix3d();
                        rotMatrix.rotX(0.0);

                        // Create the nifty LineArray
                        LineArray elementArray = new LineArray(NUM_POINTS , 
GeometryArray.COORDINATES | GeometryArray.COLOR_3);
                        elementArray.setCoordinates(0, points);
                        elementArray.setColors(0, clrs);
                        elementArray.setCapability(GeometryArray.ALLOW_COLOR_WRITE);

                        Appearance appearance = new Appearance();
                        Material m = new Material();
                        m.setLightingEnable(false);
                        appearance.setMaterial(m);

                        // Make it pickable because, well, that's what is needed in the
                        // application.
                        Geometry geom = elementArray;
                        Shape3D level = new Shape3D(geom, appearance);
                        level.setPickable(true);
                        PickTool.setCapabilities(level, PickTool.INTERSECT_FULL);

                        // Shift everything so that the center is at (0, 0), and then 
add
                        // the objects for this level.
                        innerGroup = new TransformGroup();
                        innerTrans = new Transform3D();
                        innerTrans.set(rotMatrix, new Vector3d(-base.x - 
X_DEVIATION/2.0, -base.y - Y_DEVIATION/2.0, 0.0), 1.0);
                        innerGroup.setTransform(innerTrans);
                        try {
                                innerGroup.addChild(level);
                                innerGroup.addChild(new Text("Bork", Y_DEVIATION/5.0, 
new Point3d(0.0, 0.0, 0.0)));
                        }
                        catch (javax.media.j3d.MultipleParentException exp) {}

                        // Now move stuff back to the original position.
                        t3d.set(rotMatrix, new Vector3d(base.x + X_DEVIATION/2.0, 
base.y + Y_DEVIATION/2.0, 0.0), 1.0);
                        objRoot.setTransform(t3d);
                        objRoot.addChild(innerGroup);

                        objRoot.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

                        try {
                                this.addChild(objRoot);
                                this.compile();
                        }
                        catch (Exception exp) {exp.printStackTrace();}
                } // End of Block

                /**
                 *  Tilt the level about the x-axis
                 */
                protected void adjustTilt(double delta) {
                        rotMatrix.setZero();
                        rotMatrix.rotX(delta);
                        t3d.get(tempMatrix);
                        rotMatrix.mul(tempMatrix);

                        t3d.setRotation(rotMatrix);
                        objRoot.setTransform(t3d);
                } // End of Block.adjustTilt

                /**
                 *  Spin the level about the y-axis.  Kind of like it's doing a pole 
dance!
                 */
                protected void adjustSpin(double delta) {
                        rotMatrix.setZero();
                        rotMatrix.rotY(delta);
                        t3d.get(tempMatrix);
                        rotMatrix.mul(tempMatrix);

                        t3d.setRotation(rotMatrix);
                        objRoot.setTransform(t3d);
                } // End of Block.adjustSpin
        } // End of class Block

        /**
         *  Quick class to add Text to the level.  The Text is interesting because
         *  when the LineArray goes away, the text remains.  Very interesting, indeed.
         */
        public class Text extends TransformGroup {
                protected Text2D textObject = null;
                protected static final Color3f color = new Color3f(0.2f, 0.4f, 0.2f);

                public Text(String textString, double height, Point3d location) {
                        double scale = (300.0 / 50.0) * height;
                        textObject = new Text2D(textString, color, "SansSerif",  50, 
java.awt.Font.PLAIN);

                        TransformGroup spinTg = new TransformGroup();
                        spinTg.setPickable(true);

                        spinTg.addChild(textObject);

                        Transform3D t3d = new Transform3D();
                        Matrix3f tempRot = new Matrix3f();
                        tempRot.rotX(0.0f);

                        t3d.set(tempRot, new Vector3d(location.x, location.y, 0.0), 
scale);
                        this.setTransform(t3d);

                        this.addChild(spinTg);
                }// End of Text
        } // End of class Text

        /**
         *  Create the test, make it big enough to see, and plop it on the screen.
         */
        public static void main(String[] args) {
                TestBlank youCantSeeMe = new TestBlank();
                youCantSeeMe.setBounds(0, 0, 1024, 780);
                youCantSeeMe.setVisible(true);
        } // End of main
} // End of class TestBlank

Reply via email to