ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Playing Movies in a Java 3D World, Part 1
Pages: 1, 2, 3

4. Creating the Movie Screen

JMFMovieScreen is a subclass of Java 3D's Shape3D class, so must specify a geometry and appearance for its shape.

The geometry is a quadrilateral (quad) with sides proportional to the movie's image size, but with a maximum dimension (width or height) specified as an argument to the constructor. The quad is upright, facing along the +z axis, and can be positioned anywhere on the floor.

The quad's appearance is two-sided, allowing the movie to be seen on the screen's front and back. The texture is smoothed using bilinear interpolation, which greatly reduces the pixelation of the movie image when viewed up close.

Most of this functionality is copied from the ImageCsSeries class used in the first-person shooter (FPS) example in Chapter 24 of KGPJ. ImageCsSeries displays a series of GIF images on a quad. For the sake of brevity, I'll only describe the features of JMFMovieScreen that differ from ImageCsSeries.

Rendering the Image Efficiently

A frame from the movie is laid over the quad by being converted to a texture; this is done in two steps: first the supplied BufferedImage is passed to a Java 3D ImageComponent2D object, and then to a Java 3D Texture2D.

The updating of the quad's texture occurs quite rapidly: there are 25 frame updates per second, requiring 25 changes to the texture. It's therefore quite important that the texturing be carried out efficiently. This is possible by ensuring that certain formats are utilized for the BufferedImage and ImageComponent2D objects.

The ImageComponent2D object used by JMFMovieScreen is declared like so:

ImageComponent2D ic = new ImageComponent2D(
        FORMAT_SIZE, FORMAT_SIZE, true, true);

The last two arguments of the constructor specify that it uses the "by reference" and "Y-up" modes. These modes reduce the memory needed to store the texture image, since Java 3D will avoid copying the image from application space into graphics memory.

In a Windows OS environment, using OpenGL as the underlying rendering engine in Java 3D, the ImageComponent2D format should be ImageComponent2D.FORMAT_RGB (as shown above), and the BufferedImage format should be BufferedImage.TYPE_3BYTE_BGR. The BufferedImage format is fixed in JMFSnapper.

More details on this technique, and other performance tips, can be found at j3d.org.

Linking a Texture to the Quad

The usual way of tying a texture (image) to a quad is to link the lower left corner of the texture to the lower left corner of the quad, and specify the other connections in a counter-clockwise direction. This approach is illustrated by Figure 4.

Figure 4
Figure 4. The standard linkage between texture and quad

The texture coordinates range between 0 and 1 along the x- and y- axes, with the y-axis pointing upwards. For example, the lower left corner of the texture uses the coordinate (0,0), and the top right corner is at (1,1).

When the "Y-up" mode is employed, the y-axis of the texture coordinates is reversed, to point downwards. This means that the texture coordinate (0,0) refers to the top left of the texture, while (1,1) refers to the bottom right.

With the "Y-up" mode set, the texture coordinates must be assigned to different points on the quad in order to obtain the same orientation for the image. This new configuration is shown in Figure 5.

Figure 5
Figure 5. The linkage between texture and quad when "Y-up" mode is used

The JMFMovieScreen code that connects the quad points and the texture coordinates is:

TexCoord2f q = new TexCoord2f();

q.set(0.0f, 0.0f);    
plane.setTextureCoordinate(0, 3, q);  
  // (0,0) tex coord --> top left quad point (p3)

q.set(1.0f, 0.0f);   
plane.setTextureCoordinate(0, 2, q);  
       // (1,0) --> top right (p2)

q.set(1.0f, 1.0f);    
plane.setTextureCoordinate(0, 1, q);  
       // (1,1) --> bottom right (p1)

q.set(0.0f, 1.0f);   
plane.setTextureCoordinate(0, 0, q);  
      // (0,1) --> bottom left (p0)

The plane object represents the quad.

Updating the Image

As explained earlier, a TimeBehavior object is set to call JMFMovieScreen's nextFrame() method every 40 milliseconds. nextFrame() calls getFrame() in the JMFSnapper object to retrieve the current movie frame as a BufferedImage object. This is assigned to an ImageComponent2D object, and then to the quad's texture. nextFrame() is:

// globals
private Texture2D texture;   // used by the quad
private ImageComponent2D ic;

private JMFSnapper snapper;    
                    // to take snaps of the movie
private boolean isStopped = false;  
                    // is the movie stopped?

public void nextFrame()
{ if (isStopped)   // movie has been stopped
  BufferedImage im = snapper.getFrame();  
                          // get current frame
  if (im != null) {
    ic.set(im); //assign frame to ImageComponent2D
                   // make it the shape's texture
    System.out.println("Null BufferedImage");

snapper, the JMFSnapper object, is created in JMFMovieScreen's constructor:

// load and play the movie
snapper = new JMFSnapper(movieFnm);

JMFSnapper's simple interface hides the complexity of the JMF code required to play the movie and extract frames from it. In part two of this series, JMFSnapper is replaced by a version using QuickTime for Java, with minimal changes required to JMFMovieScreen.

Pages: 1, 2, 3

Next Pagearrow