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(
ImageComponent2D.FORMAT_RGB,
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. 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. 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
return;
BufferedImage im = snapper.getFrame();
// get current frame
if (im != null) {
ic.set(im); //assign frame to ImageComponent2D
texture.setImage(0,ic);
// make it the shape's texture
}
else
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.