Texture3D by Reference: Does it work? (source included)

Howdy Folks,

I am working on texture by reference. My 2D version works, but the 3D does nothing (no exceptions, no errors, nothing). The 3D image remains the same. I am properly following the requirements for this as outlined in the docs and on postings. I am calling updateData from an ImageComponent and doing the updating in the updateData function of a class implementing ImageComponent3D.Updater. Unforunately, there aren’t any examples I have seen in which this has been done on the web.

Would any of you be willing to take a look at my test case to see if you can figure out what the heck is going on? Thanks. At a minimum, this will let those of you who want to do 2D byref have an example to go by.

I will attach the code as 3 .txt files. Rename each to .java and put in a package named help if you want to run this code. There is also an attached png that needs to go in the help package. I will be posting the code here subsequently for those who just want to look.

Thanks again!

Here’s the source for the main function. It is just boilerplate:


/**
 * 
 */
package help;

import java.awt.*;
import java.awt.event.*;

import javax.media.j3d.*;
import javax.swing.*;
import javax.vecmath.*;

import com.sun.j3d.utils.universe.SimpleUniverse;

/**
 * @author Mark Bastian
 *
 */
public class TextureByrefTester extends JFrame
{
    private static final long serialVersionUID = 3481159812056854703L;
    
    public TextureByrefTester()
    {
        super("TextureTest");
        
        setSize(800, 600);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        //Create canvas
        GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
        Canvas3D canvas = new Canvas3D(config);
        
        //Create universe
        final SimpleUniverse universe = new SimpleUniverse(canvas);
        universe.getViewingPlatform().setNominalViewingTransform();
        
        setLayout(new BorderLayout());
        add(canvas, BorderLayout.CENTER);
        
        //Add the GUI stuff needed to generate the nodes
        JPanel panel = new JPanel();
        add(panel, BorderLayout.SOUTH);
        
        JButton btn2D = new JButton("2D byRef");
        btn2D.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent event)
            {
                BranchGroup bg = new BranchGroup();
                bg.addChild(new ByRef2D());
                universe.addBranchGraph(bg);
            }
        });
        panel.add(btn2D);
        
        JButton btn3D = new JButton("3D byRef");
        btn3D.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent event)
            {
                BranchGroup bg = new BranchGroup();
                bg.addChild(new ByRef3D());
                universe.addBranchGraph(bg);
            }
        });
        panel.add(btn3D);
    }
    
    public static Bounds getBoundingSphere()
    {
        return new BoundingSphere(new Point3d(), 100.0);
    }
    
    /**
     * @param args
     */
    public static void main(String[] args)
    {
        new TextureByrefTester().setVisible(true);
    }
}

And here’s the source for the 2D version. It works just fine. Note the correct usage (If I understand correctly) of the ImageComponent2D.Updater interface to update the data.


/**
 * 
 */
package help;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;

import javax.imageio.ImageIO;
import javax.media.j3d.*;
import javax.vecmath.*;

import com.sun.j3d.utils.image.TextureLoader;

/**
 * @author Mark Bastian
 *
 */
public class ByRef2D extends TransformGroup implements ImageComponent2D.Updater
{
    public ByRef2D()
    {
        //Create a square with the right texture coordinates
        GeometryArray array = createGeometry();
      
        //Load the texture
        Texture texture = createTexture();
        
        //Create the appearance of the shape
        Appearance appearance = createAppearance(texture);
        appearance.getTexture().getImage(0).setCapability(ImageComponent.ALLOW_IMAGE_READ);
        appearance.getTexture().getImage(0).setCapability(ImageComponent.ALLOW_IMAGE_WRITE);
        
        //Create and add the shape
        Shape3D shape = new Shape3D(array, appearance);
        addChild(shape);
        
        addBehavior(appearance);
    }
    
    private GeometryArray createGeometry()
    {
        Point3f[] points = new Point3f[4];
        float dim = 0.5f;
        points[0] = new Point3f(-dim, -dim, 0.0f);
        points[1] = new Point3f( dim, -dim, 0.0f);
        points[2] = new Point3f( dim,  dim, 0.0f);
        points[3] = new Point3f(-dim,  dim, 0.0f);
        
        TexCoord2f[] texCoords = new TexCoord2f[4];
        texCoords[0] = new TexCoord2f(0.0f, 0.0f);
        texCoords[1] = new TexCoord2f(1.0f, 0.0f);
        texCoords[2] = new TexCoord2f(1.0f, 1.0f);
        texCoords[3] = new TexCoord2f(0.0f, 1.0f);
        
        QuadArray array = new QuadArray(4, GeometryArray.COORDINATES | GeometryArray.TEXTURE_COORDINATE_2);
        array.setCoordinates(0, points);
        array.setTextureCoordinates(0, 0, texCoords);
        
        return array;
    }
    
    private Texture createTexture()
    {
        URL url = ByRef2D.class.getResource("/help/red.png");
        BufferedImage image = null;
        try
        {
            image = ImageIO.read(url);
        } catch(IOException e)
        {
            e.printStackTrace();
        }
        
        TextureLoader loader = new TextureLoader(image, TextureLoader.BY_REFERENCE);
        return loader.getTexture();
    }
    
    private Appearance createAppearance(Texture texture)
    {
        //This is an essential ingredient to make sure the texture gets blended correctly
        TransparencyAttributes transAttrib = new TransparencyAttributes();
        transAttrib.setTransparencyMode(TransparencyAttributes.BLENDED);
        
        //We want to show the back side of the polygon
        PolygonAttributes polygonAttrib = new PolygonAttributes();
        polygonAttrib.setCullFace(PolygonAttributes.CULL_NONE);
        
        Appearance appearance = new Appearance();
        appearance.setTransparencyAttributes(transAttrib);
        appearance.setPolygonAttributes(polygonAttrib);
        appearance.setTexture(texture);
        
        return appearance;
    }

    private void addBehavior(final Appearance appearance)
    {
        final WakeupCondition condition = new WakeupOnElapsedTime(100);
        
        Behavior behavior = new Behavior()
        {
            @Override
            public void initialize()
            {
                wakeupOn(condition);
            }

            @Override
            public void processStimulus(Enumeration arg0)
            {
                ImageComponent2D component = (ImageComponent2D)appearance.getTexture().getImage(0);
                component.updateData(ByRef2D.this, 0, 0, component.getWidth(), component.getHeight());
            }
            
        };
        
        behavior.setSchedulingBounds(TextureByrefTester.getBoundingSphere());
        addChild(behavior);
    }
    
    public void updateData(ImageComponent2D component, int x, int y, int width, int height)
    {
        BufferedImage image = component.getImage();
        
        for(int i = 0; i < width; i++)
        {
            for(int j = 0; j < height; j++)
            {
                Color c = new Color((int)(Math.random() * 256), (int)(Math.random() * 256), (int)(Math.random() * 256));
                image.setRGB(i, j, c.getRGB());
            }
        }
    }
}

Finally, here’s the source for the nonfunctional 3D version. Note the correct usage (If I understand correctly) of the ImageComponent3D.Updater interface to update the data.


/**
 * 
 */
package help;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;

import javax.imageio.ImageIO;
import javax.media.j3d.*;
import javax.vecmath.*;

import com.sun.j3d.utils.behaviors.mouse.MouseRotate;

/**
 * @author Mark Bastian
 *
 */
public class ByRef3D extends TransformGroup implements ImageComponent3D.Updater
{
    public ByRef3D()
    {
        Appearance appearance = createAppearance();
        
        //Experimental behavior that modifies the image data
        addBehavior(appearance);
        
        GeometryArray[] geometry = createGeometry();
        
        for(int i = 0; i < geometry.length; i++)
        {
                //Create and add the shape
                Shape3D shape = new Shape3D(geometry[i], appearance);
                addChild(shape);
        }
        
        //All of this is unimportant.  It is just here to allow some mousing.
        Transform3D transform = new Transform3D();
        transform.setScale(0.5);
        setTransform(transform);
        setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        MouseRotate mb = new MouseRotate();
        mb.setSchedulingBounds(TextureByrefTester.getBoundingSphere());
        mb.setTransformGroup(this);
        this.addChild(mb);
    }
    
    /**
     * Create the 3D texture
     * @return created 3D texture
     */
    private Texture createTexture()
    {
        BufferedImage[] images = new BufferedImage[4];
        URL url0 = ByRef2D.class.getResource("/help/red.png");
        URL url1 = ByRef2D.class.getResource("/help/red.png");
        URL url2 = ByRef2D.class.getResource("/help/red.png");
        URL url3 = ByRef2D.class.getResource("/help/red.png");
        try
        {
            images[0] = ImageIO.read(url0);
            images[1] = ImageIO.read(url1);
            images[2] = ImageIO.read(url2);
            images[3] = ImageIO.read(url3);
        } catch(IOException e)
        {
            e.printStackTrace();
        }
        
        //Create the image data
        ImageComponent3D component = new ImageComponent3D(ImageComponent.FORMAT_RGBA, images, true, false);
        component.setCapability(ImageComponent.ALLOW_IMAGE_READ);
        component.setCapability(ImageComponent.ALLOW_IMAGE_WRITE);
        
        //Create the texture itself from the image component
        Texture3D texture = new Texture3D(Texture.BASE_LEVEL, Texture.RGBA, 
                images[0].getWidth(), images[0].getHeight(), images.length);
        texture.setImage(0, component);
        
        return texture;
    }
    
    private Appearance createAppearance()
    {   
        //This is an essential ingredient to make sure the texture gets blended correctly
        TransparencyAttributes transAttrib = new TransparencyAttributes();
        transAttrib.setTransparencyMode(TransparencyAttributes.BLENDED);
        
        //Texture attributes
        TextureAttributes textureAttrib = new TextureAttributes();
        textureAttrib.setCapability(TextureAttributes.ALLOW_TRANSFORM_READ);
        textureAttrib.setCapability(TextureAttributes.ALLOW_TRANSFORM_WRITE);
        Transform3D textureTransform = new Transform3D();
        textureAttrib.setTextureTransform(textureTransform);
        
        //We want to show the back side of the polygon
        PolygonAttributes polygonAttrib = new PolygonAttributes();
        polygonAttrib.setCullFace(PolygonAttributes.CULL_NONE);
        
        Appearance appearance = new Appearance();
        appearance.setTransparencyAttributes(transAttrib);
        appearance.setPolygonAttributes(polygonAttrib);
        appearance.setTextureAttributes(textureAttrib);
        appearance.setTexture(createTexture());
        
        return appearance;
    }
    
    private GeometryArray[] createGeometry()
    {
        int dim = 128;
        GeometryArray[] geometry = new GeometryArray[dim];
        
        float w = 1.0f;
        float start = -w;
        float delta = 2.0f * w / (dim - 1);
        
        for(int i = 0; i < dim; i++)
        {
            float value = start + i * delta;
            geometry[i] = createQuad(value);
        }
        
        return geometry;
    }
    
    private GeometryArray createQuad(float z)
    {
        Point3f[] points = new Point3f[4];
        float dim = 1.0f;
        points[0] = new Point3f(-dim, -dim, z);
        points[1] = new Point3f( dim, -dim, z);
        points[2] = new Point3f( dim,  dim, z);
        points[3] = new Point3f(-dim,  dim, z);
        
        z = (z + 1.0f) * 0.5f;
        TexCoord3f[] texCoords = new TexCoord3f[4];
        texCoords[0] = new TexCoord3f(0.0f, 0.0f, z);
        texCoords[1] = new TexCoord3f(1.0f, 0.0f, z);
        texCoords[2] = new TexCoord3f(1.0f, 1.0f, z);
        texCoords[3] = new TexCoord3f(0.0f, 1.0f, z);
        
        QuadArray array = new QuadArray(4, GeometryArray.COORDINATES | GeometryArray.TEXTURE_COORDINATE_3);
        array.setCoordinates(0, points);
        array.setTextureCoordinates(0, 0, texCoords);
        
        return array;
    }
    
    private void addBehavior(final Appearance appearance)
    {
        final WakeupCondition condition = new WakeupOnElapsedTime(100);
        
        Behavior behavior = new Behavior()
        {
            @Override
            public void initialize()
            {
                wakeupOn(condition);
            }

            @Override
            public void processStimulus(Enumeration enumeration)
            {
                ImageComponent3D component = (ImageComponent3D)appearance.getTexture().getImage(0);
                for(int i = 0; i < 4; i++)
                {
                    component.updateData(ByRef3D.this, i, 0, 0, component.getWidth(), component.getHeight());
                }
            }
            
        };
        
        behavior.setSchedulingBounds(TextureByrefTester.getBoundingSphere());
        addChild(behavior);
    }

    public void updateData(ImageComponent3D component, int index, int x, int y, int width, int height)
    {
        BufferedImage image = component.getImage(index);
        
        for(int i = 0; i < width; i++)
        {
            for(int j = 0; j < height; j++)
            {
                Color c = new Color((int)(Math.random() * 256), (int)(Math.random() * 256), (int)(Math.random() * 256));
                image.setRGB(i, j, c.getRGB());
            }
        }
    }
}

Yes

Best answer ever!

Have you verified that your video card supports 3D textures? You can use the canvas3d queryProperties to query for ‘texture3DAvailable’.

Mike

FYI: This was determined to be a bug in Java3D but is now fixed.