Using the Java3D Framework

Java3D is actually pretty easy to use, at least compared to OpenGL. Then again, it looks like Java3D hasn’t been updated in a couple of years, since the splash screen still has the Sun logo on it. Then again again, the OpenGL people are still telling people to use libraries that were last updated in 1994, only don’t tell them I pointed that out, because they have an attitude about it. So pick your poison. For my part, I generally prefer benign negligence over unjustified arrogance.

1. Download the latest version of the Java3D installer. As of this writing, it is available at http://www.oracle.com/technetwork/java/javase/tech/index-jsp-138252.html.

2. Navigate to the directory to which the installer was downloaded and double-click to start it. Follow the prompts.

3. In any convenient location, create a new directory named “Java3DTest”.

4. In the newly created Java3DTest directory, create a new text file named “Java3DTest.java”, containing the (copious) source code included at the bottom of these instructions.

5. Copy the JavaPathAndProgramNameSet.bat, ProgramBuild.bat, and ProgramRun.bat files to the Java3DTest directory. These files were created in a previous tutorial.

6. Double-click the icon for ProgramBuild.bat to run it. The code will be compiled into class files.

7. Double-click the icon for ProgramRun.bat to run it. A window will appear containing a textured cube, as shown in the image below.

Code Listing:

import com.sun.j3d.utils.universe.*;
import java.awt.image.*; 
import javax.media.j3d.*;
import javax.vecmath.*;

public class Java3DTest 
{
    private SimpleUniverse simpleUniverse;
    private Transform3D transformCamera;

    public static void main(String[] args)
    {
        new Java3DTest().mainProcedure();
    }

    private void mainProcedure()
    {
        Canvas3D canvas = new Canvas3D(SimpleUniverse.getPreferredConfiguration());

        this.simpleUniverse = this.buildUniverse(canvas);

        Camera camera = this.buildCamera();

        this.transformCamera = new Transform3D();

        TransformGroup transformGroupCamera = this.buildTransformGroupForCamera(this.transformCamera);

        BufferedImage textureImage = this.buildTextureImage();

        Mesh mesh = this.buildMesh(camera.viewSize.x / 2, textureImage);

        BranchGroup branchGroupForMesh = this.convertMeshToBranchGroup(mesh);

        BranchGroup scene = this.buildScene
        (
            transformGroupCamera, 
            branchGroupForMesh
        );
        this.simpleUniverse.addBranchGraph(scene);

        this.initializeViewForCamera(camera);

        this.buildAndShowFrame(camera, canvas);
    }

    private SimpleUniverse buildUniverse(Canvas3D canvas)
    {
        SimpleUniverse simpleUniverse = new SimpleUniverse(canvas);
        simpleUniverse.getViewingPlatform().setNominalViewingTransform();

        return simpleUniverse;
    }

    private TransformGroup buildTransformGroupForCamera(Transform3D transformCamera)
    {
        TransformGroup transformGroupCamera = new TransformGroup();
        transformGroupCamera.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        transformGroupCamera.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        transformGroupCamera.setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND);
        transformGroupCamera.setCapability(TransformGroup.ALLOW_CHILDREN_WRITE);
        transformGroupCamera.setTransform(transformCamera);

        return transformGroupCamera;
    }

    private BufferedImage buildTextureImage()
    {
        // builds a test image for the mesh texture
        // a 6 x 1 grid of blue numerals over red circles

        BufferedImage textureImage = null;

        int numberOfFaces = 6;
        int textureDimensionForFace = 256;
        int textureDimensionForFaceHalf = textureDimensionForFace / 2;

        textureImage = new BufferedImage
        (
            textureDimensionForFace * numberOfFaces, 
            textureDimensionForFace, 
            BufferedImage.TYPE_INT_ARGB
        );

        java.awt.Graphics2D graphics = textureImage.createGraphics();

        graphics.setColor(java.awt.Color.WHITE);
        graphics.fillRect(0, 0, textureDimensionForFace * 6, textureDimensionForFace);

        graphics.setFont(new java.awt.Font("Arial", java.awt.Font.BOLD, textureDimensionForFace));

        for (int f = 0; f < numberOfFaces; f++)
        {
            int faceOffsetX = f  * textureDimensionForFace;

            graphics.setColor(java.awt.Color.RED);
            graphics.fillOval
            (
                faceOffsetX,
                0,
                textureDimensionForFace,
                textureDimensionForFace
            );

            graphics.setColor(java.awt.Color.BLUE);
            graphics.drawString
            (
                " " + f, 
                (int)faceOffsetX, 
                (int)(.85 * textureDimensionForFace)
            );
        }

        graphics.dispose();

        return textureImage;
    }

    private Mesh buildMesh
    (
        double dimensionOfCubeInPixels,
        BufferedImage textureImage
    )
    {
        // builds a test mesh
        // a cube whose faces each display one cell
        // from the 6 x 1 test texture

        Point3d[] vertexPositions = new Point3d[]
        {
            new Point3d(-1, -1, -1),
            new Point3d(1, -1, -1),
            new Point3d(1, 1, -1),
            new Point3d(-1, 1, -1),

            new Point3d(-1, -1, 1),
            new Point3d(1, -1, 1),
            new Point3d(1, 1, 1),
            new Point3d(-1, 1, 1),
        };

        double dimensionHalf = dimensionOfCubeInPixels / 2;

        for (int i = 0; i < vertexPositions.length; i++)
        {
            Point3d vertexPosOld = vertexPositions[i];

            vertexPositions[i] = new Point3d
            (
                dimensionHalf * vertexPosOld.x, 
                dimensionHalf * vertexPosOld.y, 
                dimensionHalf * vertexPosOld.z
            );
        }

        int[][] vertexIndicesForFaces = new int[][]
        {
            new int[] { 0, 1, 2, 3 }, // bottom

            // sides
            new int[] { 0, 1, 5, 4 },
            new int[] { 1, 2, 6, 5 },
            new int[] { 2, 3, 7, 6 },
            new int[] { 3, 0, 4, 7 },

            new int[] { 4, 5, 6, 7 }, // top
        };

        int numberOfFaces = vertexIndicesForFaces.length;

        // each face of the cube shows the entire texture
        // from uv (0, 0) at one vertex 
        // to (1, 1) at the opposite vertex

        TexCoord2f[][] textureUVsForFaceVertices = new TexCoord2f[numberOfFaces][];

        float widthOfFaceAsFractionOfTotal = (float)1 / (float)numberOfFaces;
        for (int f = 0; f < numberOfFaces; f++)
        {
            float textureUForCornerNear = (float)f * widthOfFaceAsFractionOfTotal;
            float textureUForCornerFar = textureUForCornerNear + widthOfFaceAsFractionOfTotal;

            TexCoord2f[] textureUVsForFace = new TexCoord2f[]
            {
                new TexCoord2f(textureUForCornerNear, (float)0),
                new TexCoord2f(textureUForCornerFar, (float)0),
                new TexCoord2f(textureUForCornerFar, (float)1),
                new TexCoord2f(textureUForCornerNear, (float)1),
            };

            textureUVsForFaceVertices[f] = textureUVsForFace;
        }

        Mesh returnMesh = new Mesh
        (
            vertexPositions,

            vertexIndicesForFaces,

            textureImage,

            textureUVsForFaceVertices
        );

        return returnMesh;
    }

    private BranchGroup convertMeshToBranchGroup(Mesh mesh)
    {
        int numberOfFaces = mesh.vertexIndicesForFaces.length;

        QuadArray quadArray = new QuadArray
        (
            numberOfFaces * 4, 
            QuadArray.COORDINATES | GeometryArray.TEXTURE_COORDINATE_2
        );

        for (int f = 0; f < numberOfFaces; f++)
        { 
            int[] vertexIndicesForFace = mesh.vertexIndicesForFaces[f];
            TexCoord2f[]  textureUVsForFace = mesh.textureUVsForFaceVertices[f];

            int fTimes4 = f * 4;

            for (int vi = 0; vi < vertexIndicesForFace.length; vi++)
            {
                int vertexIndex = vertexIndicesForFace[vi];
                Point3d vertexPos = mesh.vertexPositions[vertexIndex];
                TexCoord2f textureUV = textureUVsForFace[vi];

                quadArray.setCoordinate
                (
                    fTimes4 + vi, 
                    vertexPos
                );

                quadArray.setTextureCoordinate
                (
                    0,
                    fTimes4 + vi,
                    textureUV 
                );
            }
        }

        Appearance appearance = new Appearance();

        appearance.setColoringAttributes
        (
            new ColoringAttributes
            (
                new Color3f(.5f, .5f, .5f), 
                ColoringAttributes.NICEST
            )
        );

        Color3f colorWhite = new Color3f(1f, 1f, 1f);

        Material material = new Material
        (
            colorWhite, 
            colorWhite,
            colorWhite, 
            colorWhite,
            0.5f
        );

        appearance.setMaterial
        (
            material
        );

        javax.media.j3d.Texture systemTexture = new com.sun.j3d.utils.image.TextureLoader
        (
            mesh.textureImage
        ).getTexture();

        appearance.setTexture(systemTexture);

        Shape3D shapeForQuadArray = new Shape3D(quadArray, appearance);

        // to be implemented
        Transform3D transform = new Transform3D();

        TransformGroup transformGroup = new TransformGroup();
        transformGroup.setTransform(transform);
        transformGroup.addChild(shapeForQuadArray);

        BranchGroup branchGroup = new BranchGroup();
        branchGroup.addChild(transformGroup);
        branchGroup.compile();

        return branchGroup;
    }

    private BranchGroup buildScene
    (
        TransformGroup transformGroupCamera, 
        BranchGroup branchGroupForMesh
    )
    {
        BranchGroup scene = new BranchGroup();
        scene.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        scene.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        scene.addChild(transformGroupCamera);
        scene.addChild(branchGroupForMesh);
        scene.compile();

        return scene;        
    }

    private Camera buildCamera()
    {
        Camera camera = new Camera
        (
            new Point3d(320, 240, 1024), // viewSize
            160, // distanceToViewPlane
            new Point3d(320, 320, 320), // pos
            new Point3d(0, 0, 0), // lookTarget
            new Vector3d(0, 0, 1) // up
        );

        return camera;
    }

    private void initializeViewForCamera(Camera camera)
    {
        View view = this.simpleUniverse.getViewer().getView();
        view.setFrontClipPolicy(View.VIRTUAL_EYE);
        view.setBackClipPolicy(View.VIRTUAL_EYE);

        this.setViewpointForCamera
        (
            camera
        );
    }

    private void buildAndShowFrame(Camera camera, Canvas3D canvas)
    {
        javax.swing.JFrame frame = new javax.swing.JFrame();

        javax.swing.JPanel panelMain = new javax.swing.JPanel();
        panelMain.setLayout(new java.awt.BorderLayout());
        panelMain.add(canvas, java.awt.BorderLayout.CENTER);

        frame.add(panelMain);
        frame.setContentPane(panelMain);

        frame.pack();
        frame.setVisible(true);
        frame.setSize
        (
            (int)(camera.viewSize.x), 
            (int)(camera.viewSize.y)
        );
        frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
    }

    public void setViewpointForCamera
    (
        Camera camera
    )
    {
        View view = this.simpleUniverse.getViewer().getView();

        view.setFieldOfView
        (
            Math.atan(camera.viewSize.x / 2 / camera.distanceToViewPlane)
        );

        view.setFrontClipDistance(0.1);
        view.setBackClipDistance(camera.viewSize.z);

        this.transformCamera.lookAt
        (
            camera.pos,
            camera.lookTarget,
            camera.up
        );        

        this.transformCamera.invert();

        TransformGroup viewPlatformTransformGroup = this.simpleUniverse.getViewingPlatform().getViewPlatformTransform();
        viewPlatformTransformGroup.setTransform(this.transformCamera);
    }
}

class Camera
{
    public Point3d viewSize;
    public double distanceToViewPlane;
    public Point3d pos;
    public Point3d lookTarget;
    public Vector3d up;

    public Camera
    (
        Point3d viewSize,
        double distanceToViewPlane,
        Point3d pos,
        Point3d lookTarget,
        Vector3d up
    )
    {
        this.viewSize = viewSize;
        this.distanceToViewPlane = distanceToViewPlane;

        this.pos = pos;
        this.lookTarget = lookTarget;
        this.up = up;
    }
}

class Mesh
{
    public Point3d[] vertexPositions;
    public int[][] vertexIndicesForFaces;
    public BufferedImage textureImage;
    public TexCoord2f[][] textureUVsForFaceVertices;

    public Mesh
    (
        Point3d[] vertexPositions,
        int[][] vertexIndicesForFaces,
        BufferedImage textureImage,
        TexCoord2f[][] textureUVsForFaceVertices
    )
    {
        this.vertexPositions = vertexPositions;
        this.vertexIndicesForFaces = vertexIndicesForFaces;
        this.textureImage = textureImage;
        this.textureUVsForFaceVertices = textureUVsForFaceVertices;
    }
}
Advertisements
This entry was posted in Uncategorized and tagged , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s