Texture Mapping in Java 2D

Ever wanted uv-style texture mapping when rendering triangles or quads in Java using Swing? Okay, there's probably only a handful of people that ever wanted to do this, and I'm one. I had a project recently that looped through a number of 3D models and rendered a top-down orthogonal view for each model. I admit now that I probably should have loaded the entire model into Java3D, rendered the orthogonal sprite, then spat that back to Java2D, but my program wanted minimal overhead for running a 3D engine.  I wanted to loop through each polygon and simply render it with the appropriate texture and texture transformation.  It occurred to me later that this would be a nice feature to have in Java2D since settings up UV-coordinates for each vertex of a 2D shape is relatively painless, and the texture transformation that it provides is fairly complex, and nearly impossible to achieve in Java2D alone.

So here goes... here's my shared utility for achieving 3D-game engine style texture mapping on a 2D triangle or quad primitive. No... I don't handle arbitrary polygons, but I suspect such a solution would involve a call to the Warp operation inside JAI. Even then the transformation probably wouldn't provide seamless interpolation along the edges of the polygon like we get for the perspective-correct transformations we see in modern game engines.

The first section here contains snippets of example code and discussion. The second section contains some performance measures and discussion.  The third section contains the source code of the example and the library class (TextureMapper) as well as links to download each source file.

Example Usage

To exemplify how to render using texture coordinates, I set up a simple custom JComponent which gets added to a JFrame.  The JComponent is always sized to the full dimensions of the JFrame because of the GridLayout. Every time the paintComponent method of the JComponent gets called, I build a "polygon," which is a Vector of vertices (of the type TextureMapper.Vertex). The polygon should be left open so that the first and last vertices do not equal each other, and the traversal direction (right hand or left hand) does not matter since TextureMapper will render in both directions to avoid the culling of triangles whose normals face into the canvas. See the following listing for how the polygon is set up:

Vector v = new Vector();
v.add(new TextureMapper.Vertex(
    new Point2D.Float(0.1f * (float) getWidth(), 
              0.1f * (float) getHeight()),
    new Point2D.Float(0, 0)));
v.add(new TextureMapper.Vertex(
    new Point2D.Float(0.75f * (float) getWidth(),
                          0.75f * (float) getHeight()),
    new Point2D.Float(1f, 1f)));
v.add(new TextureMapper.Vertex(
    new Point2D.Float(0.1f * (float) getWidth(),
              0.85f * (float) getHeight()),
    new Point2D.Float(0f, 1f)));

This will create a nearly-right triangle whose top-left vertex starts at 10% of the width of the component and 10% of the height of the component; whose bottom-right vertex is at 75% the width and height of the component; and whose bottom-left vertex is at 10% of the width and 85% of the height of the component. Once rendered, the triangle has the shape of the image at the top of this article.

The real work gets done in the following lines:

int sampling = 8;
TextureMapper renderer = TextureMapper.getSharedInstance();
renderer.setAntiAliasScale(sampling);
renderer.renderShape(g2d, img, v); 

Here, img is the image that will serve as the texture source (a BufferedImage); g2d is the Graphics2D object that is an argument in the JComponent.paintComponent() method; and v is your polygon as a Vector.

It's important to note that a considerable amount of overhead goes into setting up a 3D scene in Java3D. It would be a waste of processing time and memory to rebuild that scaffolding every time you wanted to call this method.  The TextureMapper class keeps a shared instance which manages all of that for you. The implications that this has on memory and performance are shown in the next section.

Also, the anti-aliasing scale is an important parameter to pay attention to. On its own, Java3D (at the time of this writing, using 1.5) does not support anti-aliasing when rendering off-screen.  A common workaround is to render the result in a higher-than-necessary resolution, and scale it down using interpolation. The anti-aliasing scale, as set with the method TextureMapper.setAntiAliasScale(int) can be set to any power of 2, but is set to 4 by default.  Scales larger than 8 may cause serious performance problems in your program and are probably unnecessary (again, see the performance section below). A scale of 1 can be used to use no antialiasing at all.

Finally, when using the TextureMapper, it is almost always essential to turn on transparency for Java3D back buffers. Without this, your rendered triangle will look like one triangle on top of a black square background. You can turn on transparency by adding this argument when invoking the JVM: -Dj3d.transparentOffScreen=true.

Performance Analysis

I'll admit that this is certainly not the fastest trick around... it might be faster to write a new Paint derivative that performs some of this transformation for you (maybe I'll do this for another post). That said, I ran the program to produce a simple triangle that is about  80x80 pixels using various values for anti-aliasing, and the following table enumerates the results.

Anti-aliasing Scaling Avg. Speed (ms)
Avg. Memory Usage (kb)
Result
Difference Difference (0-64)
 1 68.8 747.85781xSampling 
 
 2 62.2 842.17812xSampling1x2xSamplingDifference.png1x2xSamplingDifferenceContrast64
 4 74.8 1145.4144xSampling4x2xSamplingDifference.png4x2xSamplingDifferenceContrast64
 8 90.8 2729.2848xSampling8x4xSamplingDifference8x4xSamplingDifferenceContrast64

 As you can see, as I increased the anti-aliasing scale, processing time increases (though minimally at this render size) and the amount of used memory increases exponentially. The right three columns show the rendered image, the image difference between one anti-alias scale and the next (or the change in pixel values between one anti-aliasing value and the next), and the same image but with a contrast applied so that changes can be better seen (the input values for the contrast were multiplied by 4).

At a superficial level it is easy to tell that the ideal value for anti-aliasing is either 2 or 4.  The result image for 2 shows a definite improvement (and we can see where these improvements lie by looking at the image differences) over the unscaled version, but there are still some jaggy edges around the triangle boundary and on the texture itself. The resulting image for 4 looks better than for 2 and provides almost uniform improvements wherever there are intensity edges. The image for 8, on the other hand, does not appear to provide any improvement over 4, and with the significant memory cost, it seems impractical for my uses (and probably for anyone else).

I can't say why there artifacts in my image differences unless it's due to compression that was getting applied when I saved my screenshots. Oops!

In summary, the default value of 4 is probably sufficient but if you need to be a stickler for memory, then setting the scale to 2 would be faster (maybe even faster than with no anti-aliasing... now that was a strange piece of data), it would look better, and require a minimal amount of extra memory.

Source Code

To get this to run, remember that you first must have Java3D installed in your JRE or in your project. After that, feel free to leverage the example code below (TextureMapWithSwing) and add TextureMapper to your project.

TextureMapWithSwing

 

/**
 * Copyright (c) 2009 Robert Truxler (rob *at* robgfx *dot* com)
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * The end-user documentation included with the redistribution, if any, 
 * must include the following acknowledgment: 
 * "This product includes software contributed by Robert Truxler (http://www.robgfx.com/)", 
 * in the same place and form as other third-party acknowledgments. 
 * Alternately, this acknowledgment may appear in the software user-interface itself
 * (not just source code), in the same form and location as other such third-party 
 * acknowledgments.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package com.robgfx.howto;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Vector;

import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

import com.robgfx.imageprocessing.TextureMapper;

public class TextureMapWithSwing extends JFrame {

    public TextureMapWithSwing(final BufferedImage img) {
        initUI(img);
    }

    private void initUI(final BufferedImage img) {
        JPanel p = new JPanel();
        p.setLayout(new GridLayout());
        p.add(

        new JComponent() {
            public void paintComponent(Graphics g) {
                Graphics2D g2d = (Graphics2D) g;
                
                long beforeRender = System.currentTimeMillis();
                System.gc();
                long startFreeSize = Runtime.getRuntime().freeMemory();
                Vector v = new Vector();
                v.add(new TextureMapper.Vertex(new Point2D.Float(
                        0.1f * (float) getWidth(), 0.1f * (float) getHeight()),
                        new Point2D.Float(0, 0)));
                v.add(new TextureMapper.Vertex(
                        new Point2D.Float(0.75f * (float) getWidth(),
                                0.75f * (float) getHeight()),
                        new Point2D.Float(1f, 1f)));
                v.add(new TextureMapper.Vertex(
                        new Point2D.Float(0.1f * (float) getWidth(),
                                0.85f * (float) getHeight()),
                        new Point2D.Float(0f, 1f)));
                int sampling = 8;
                TextureMapper renderer = TextureMapper.getSharedInstance();
                renderer.setAntiAliasScale(sampling);
                renderer.renderShape(g2d, img, v);
                double durationMillis = System.currentTimeMillis()
                        - beforeRender;
                long freeSizeAfter = startFreeSize
                        - Runtime.getRuntime().freeMemory();
                System.out.println("sampling: " + sampling + " duration (ms): "
                        + durationMillis + " memory used (kb): "
                        + freeSizeAfter / 1024f);
            }
        }

        );
        setContentPane(p);
        setSize(150, 150);
        setVisible(true);
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        try {
            BufferedImage img = ImageIO.read(new File("C:/testGrid.png"));
            new TextureMapWithSwing(img);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

TextureMapper

/**
 * Copyright (c) 2009 Robert Truxler (rob *at* robgfx *dot* com)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * The end-user documentation included with the redistribution, if any, must
 * include the following acknowledgment:"This product includes software contributed by Robert Truxler (http://www.robgfx.com/)"
 * , in the same place and form as other third-party acknowledgments.
 * Alternately, this acknowledgment may appear in the software user-interface
 * itself (not just source code), in the same form and location as other such
 * third-party acknowledgments.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.robgfx.imageprocessing;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.TexturePaint;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.Iterator;
import java.util.Vector;

import javax.media.j3d.Appearance;
import javax.media.j3d.Background;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.GraphicsConfigTemplate3D;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.IndexedQuadArray;
import javax.media.j3d.IndexedTriangleArray;
import javax.media.j3d.Locale;
import javax.media.j3d.Material;
import javax.media.j3d.Node;
import javax.media.j3d.PhysicalBody;
import javax.media.j3d.PhysicalEnvironment;
import javax.media.j3d.Shape3D;
import javax.media.j3d.TexCoordGeneration;
import javax.media.j3d.Texture;
import javax.media.j3d.Texture2D;
import javax.media.j3d.TextureAttributes;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TransparencyAttributes;
import javax.media.j3d.View;
import javax.media.j3d.ViewPlatform;
import javax.media.j3d.VirtualUniverse;
import javax.vecmath.Color3f;
import javax.vecmath.Point3f;
import javax.vecmath.TexCoord2f;
import javax.vecmath.Vector3f;

import com.robgfx.howto.TextureMapWithSwing;
import com.sun.j3d.utils.image.TextureLoader;

/**
 * The {@link TextureMapper} class is used for rendering quads and triangles in
 * Swing that are textured using a common technique in 3D graphics engines.
 * Traditionally, the Swing {@link Graphics2D} object provides a number of
 * methods for filling arbitrary shapes. You fill a shape with a solid color,
 * with a gradient ({@link GradientPaint}, or with a texture (
 * {@link TexturePaint}). The latter paint, {@link TexturePaint} offers
 * functionality for most texture fills (filling the shape with a tiled or
 * stretched image). {@link TextureMapper} provides a simple interface, wrapping
 * Java3D, for performing more complex texture fills that involve shears,
 * rotations, translations, and perspective transformations.
 * 
 * See the wikipedia.org page on <a>
</a> * href="http://en.wikipedia.org/wiki/Texture_mapping">texture mapping for a
 * examples and a description of what it is.
 * 
 * Since {@link TextureMapper} requires Java3D, make sure that the
 * {@link javax.media.j3d} package is within your class path and that any
 * associated native drivers are installed with your JRE.
 * 
 * <b>Important</b> In order for {@link TextureMapper} to render
 * triangles and quads correctly (and not with a black rectangle background
 * underneath), include the following JVM argument in your call to the java runtime:
 * 
 * <code>-Dj3d.transparentOffScreen=true</code>
 *
 * For an example of how to use this class, see {@link TextureMapWithSwing}. 
 * 
 * @author Robert Truxler (www.robgfx.com)
 * @see TextureMapWithSwing
 * @see javax.media.j3d
 */
public class TextureMapper {

    private static final TextureMapper textureMapper = new TextureMapper();

    private int antiAliasScale = 4;

    public static class Vertex {
        public Point2D.Float point;
        public Point2D.Float textureCoords;

        public Vertex(Point2D.Float point, Point2D.Float textureCoords) {
            this.point = point;
            this.textureCoords = textureCoords;
        }
    }

    GraphicsConfigTemplate3D template = new GraphicsConfigTemplate3D();
    private static final boolean offscreen = true;
    // set template properties here
    GraphicsConfiguration gc = GraphicsEnvironment
            .getLocalGraphicsEnvironment().getDefaultScreenDevice()
            .getBestConfiguration(template);
    private BranchGroup viewBG;
    private Locale myLocale;
    private BranchGroup geometry;
    private VirtualUniverse myUniverse;

    public static TextureMapper getSharedInstance() {
        return textureMapper;
    }

    private TextureMapper() {
        System.setProperty("j3d.transparentOffScreen", "true");
    }

    /**
     * Set the sampling scale for simulated antialiasing (default of 4, 2 will
     * use less memory).
     * 
     * @param scale
     *            This value must be a power of 2 or an IllegalArgumentException
     *            will get thrown
     */
    public void setAntiAliasScale(int scale) {
        if ((Math.log(scale) / Math.log(2)) % 1 > 0) {
            throw new IllegalArgumentException("scale must be a power of 2: "
                    + scale);
        }
        this.antiAliasScale = scale;
    }

    /**
     * 
     * @param g2Canvas
     * @param canvasBounds
     * @param texture
     * @param vertices
     */
    public synchronized void renderShape(Graphics2D g2Canvas,
            BufferedImage texture, final Vector<vertex> vertices) {
        int vertexCount = vertices.size();
        if (vertexCount != 3 && vertexCount != 4) {
            throw new IllegalArgumentException(
                    "Vertices do not specify a triangle or quad: " + vertices);
        }
        Rectangle2D bnds = getBounds(vertices);
        if (Math.round(bnds.getWidth()) == 0
                || Math.round(bnds.getHeight()) == 0)
            return;

        renderTexturedMappedShape(g2Canvas, texture, vertices);
    }

    private Rectangle2D getBounds(Vector<vertex> vertices) {
        float minX = Float.MAX_VALUE;
        float minY = Float.MAX_VALUE;
        float maxX = Float.MIN_VALUE;
        float maxY = Float.MIN_VALUE;

        for (Iterator iterator = vertices.iterator(); iterator.hasNext();) {
            Vertex vertex = (Vertex) iterator.next();
            minX = Math.min((float) vertex.point.getX(), minX);
            minY = Math.min((float) vertex.point.getY(), minY);
            maxX = Math.max((float) vertex.point.getX(), maxX);
            maxY = Math.max((float) vertex.point.getY(), maxY);
        }
        return new Rectangle2D.Float(minX, minY, maxX - minX, maxY - minY);
    }

    private void renderTexturedMappedShape(Graphics2D canvas,
            BufferedImage tex, Vector<vertex> vertices) {
        if (myCanvas3D == null) {
            myCanvas3D = new Canvas3D(gc, offscreen) {
                public void postRender() {
                }
            };
            myUniverse = new VirtualUniverse();
            viewBG = buildViewBranch(myCanvas3D);
            viewBG.setCapability(BranchGroup.ALLOW_DETACH);
        }

        if (myLocale != null) {
            myUniverse.removeAllLocales();
        }
        myLocale = new Locale(myUniverse);
        myLocale.addBranchGraph(viewBG);

        makeOffscreen(vertices);

        boolean first = true;
        if (geometry == null) {
            first = true;
            geometry = new BranchGroup();
            geometry.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
            geometry.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
            geometry.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
            geometry.setCapability(BranchGroup.ALLOW_DETACH);
        } else {
            geometry.removeAllChildren();
            if (detachOnNextPass != null && detachOnNextPass.size() > 0) {
                for (Iterator<branchgroup></branchgroup> iterator = detachOnNextPass
                        .iterator(); iterator.hasNext();) {
                    BranchGroup bg = iterator.next();
                    // detach so that one rendering doesn't get splatted on top
                    // of the next
                    bg.detach();
                }
                detachOnNextPass.clear();
            }
        }

        Vector<vertex> reversed = getReversed(vertices);

        if (vertices.size() == 3) {
            geometry.addChild(buildContentBranch(buildTriangle(tex, vertices)));
            geometry.addChild(buildContentBranch(buildTriangle(tex, reversed)));
        } else if (vertices.size() == 4) {
            geometry.addChild(buildContentBranch(buildQuad(tex, vertices)));
            geometry.addChild(buildContentBranch(buildQuad(tex, reversed)));
        }
        if (first)
            myLocale.addBranchGraph(geometry);
        textureMapPoly(canvas, vertices);
    }

    private Vector<vertex> getReversed(Vector<vertex> vertices) {
        Vector<vertex> v = new Vector<vertex>(vertices.size());
        for (int i = vertices.size() - 1; i >= 0; i--) {
            v.add(vertices.get(i));
        }
        return v;
    }

    protected Canvas3D myCanvas3D = null;
    private BufferedImage bImage;
    private Vector<branchgroup></branchgroup> detachOnNextPass;

    /**
     * This function builds the view branch of the scene graph. It creates a
     * branch group and then creates the necessary view elements to give a
     * useful view of our content.
     * 
     * @param c
     *            Canvas3D that will display the view
     * @return BranchGroup that is the root of the view elements
     */
    protected BranchGroup buildViewBranch(Canvas3D c) {

        BranchGroup viewBranch = new BranchGroup();
        Transform3D viewXfm = new Transform3D();
        viewXfm.set(new Vector3f(1f, 1f, 10.0f));
        TransformGroup viewXfmGroup = new TransformGroup(viewXfm);
        ViewPlatform myViewPlatform = new ViewPlatform();
        PhysicalBody myBody = new PhysicalBody();
        PhysicalEnvironment myEnvironment = new PhysicalEnvironment();
        viewXfmGroup.addChild(myViewPlatform);
        viewBranch.addChild(viewXfmGroup);
        if (c.getView() != null)
            c.getView().removeAllCanvas3Ds();
        viewBranch.addChild(new Background(new Color3f(Color.RED)));
        View myView = new View();
        myView.addCanvas3D(c);
        myView.attachViewPlatform(myViewPlatform);
        myView.setPhysicalBody(myBody);
        myView.setPhysicalEnvironment(myEnvironment);
        myView.setProjectionPolicy(View.PARALLEL_PROJECTION);
        return viewBranch;
    }

    /**
     * This builds the content branch of our scene graph. The shape supplied as
     * a parameter is slightly tilted to reveal its 3D shape. It also uses the
     * addLights function to add some lights to the scene.
     * 
     * @param shape
     *            Node that represents the geometry for the content
     * @return BranchGroup that is the root of the content branch
     */
    protected BranchGroup buildContentBranch(Node shape) {
        BranchGroup contentBranch = new BranchGroup();
        // Transform3D rotateCube = new Transform3D();
        // rotateCube.set(new AxisAngle4d(1.0, 1.0, 0.0, Math.PI / 4.0));
        // TransformGroup rotationGroup = new TransformGroup(rotateCube);
        contentBranch.addChild(shape);
        // rotationGroup.addChild(shape);
        // addLights(contentBranch);
        contentBranch.setCapability(BranchGroup.ALLOW_DETACH);
        if (detachOnNextPass == null) {
            detachOnNextPass = new Vector<branchgroup></branchgroup>();
        }
        detachOnNextPass.add(contentBranch);
        return contentBranch;
    }

    /**
     * This defines the appearance for the shape using a texture. It uses a
     * TextureLoader to load the texture image from an external file and a
     * TexCoordGeneration to define the texture coordinates.
     * 
     * @return Appearance that uses a texture.
     */
    protected Appearance DefineAppearance(BufferedImage textureImg) {
        // This is used to automatically define the texture coordinates.
        // The coordinates are generated in object coordinates, but by
        // commented out the line with 'OBJECT_LINEAR' and uncommenting
        // the line 'EYE_LINEAR' the program will use eye coordinates.
        TexCoordGeneration textCoorder = new TexCoordGeneration(
                TexCoordGeneration.OBJECT_LINEAR,
                // TexCoordGeneration.EYE_LINEAR,
                TexCoordGeneration.TEXTURE_COORDINATE_2);
        // Load the texture from the external image file
        TextureLoader textLoad = new TextureLoader(textureImg);
        // Access the image from the loaded texture
        ImageComponent2D textImage = textLoad.getImage();
        // Create a two dimensional texture
        Texture2D texture = new Texture2D(Texture2D.BASE_LEVEL, Texture.RGBA,
                textImage.getWidth(), textImage.getHeight());
        // Set the texture from the image loaded
        texture.setImage(0, textImage);
        // Create the appearance that will use the texture
        Appearance app = new Appearance();
        app.setTexture(texture);
        // Pass the coordinate generator to the appearance
        // Define how the texture will be mapped onto the surface
        // by creating the appropriate texture attributes
        TextureAttributes textAttr = new TextureAttributes();
        textAttr.setTextureMode(TextureAttributes.REPLACE);
        app.setTextureAttributes(textAttr);
        app.setMaterial(new Material());
        TransparencyAttributes trans = new TransparencyAttributes();
        trans.setTransparencyMode(TransparencyAttributes.BLENDED);
        app.setTransparencyAttributes(trans);
        return app;
    }

    protected Node buildQuad(BufferedImage textureImg, Vector<vertex> vertices) {
        Rectangle2D shapeBounds = getBounds(vertices);
        IndexedQuadArray tri = new IndexedQuadArray(4,
                IndexedQuadArray.COORDINATES
                        | IndexedQuadArray.TEXTURE_COORDINATE_2, 1, new int[] {
                        0, 0 }, 4);
        Point3f[] triCoords = {
                getCoordInCanvasSpace(shapeBounds, vertices.get(0)),
                getCoordInCanvasSpace(shapeBounds, vertices.get(1)),
                getCoordInCanvasSpace(shapeBounds, vertices.get(2)),
                getCoordInCanvasSpace(shapeBounds, vertices.get(3)) };
        TexCoord2f[] textureCoordinates = {
                new TexCoord2f(vertices.get(0).textureCoords.x, 1 - vertices
                        .get(0).textureCoords.y),
                new TexCoord2f(vertices.get(1).textureCoords.x, 1 - vertices
                        .get(1).textureCoords.y),
                new TexCoord2f(vertices.get(2).textureCoords.x, 1 - vertices
                        .get(2).textureCoords.y),
                new TexCoord2f(vertices.get(3).textureCoords.x, 1 - vertices
                        .get(3).textureCoords.y) };

        int coordIndices[] = { 0, 1, 2, 3 };
        int texCoordIndices[] = { 0, 1, 2, 3 };
        tri.setCoordinates(0, triCoords);
        tri.setCoordinateIndices(0, coordIndices);

        tri.setTextureCoordinates(0, 0, textureCoordinates);
        tri.setTextureCoordinateIndices(0, 0, texCoordIndices);
        return new Shape3D(tri, DefineAppearance(textureImg));
    }

    /**
     * The Java2D point in vertex is a measure of pixels across the canvas. We
     * need to convert this to a floating point value that spans from 0..2
     * 
     * @param canvas
     * @param vertex
     * @return
     */
    private Point3f getCoordInCanvasSpace(Rectangle2D shapeBounds, Vertex vertex) {
        float xPx = vertex.point.x;
        float yPx = vertex.point.y;
        float xF = (float) (2 * (xPx - shapeBounds.getX()) / shapeBounds
                .getWidth());
        float yF = (float) (2 * (yPx - shapeBounds.getY()) / shapeBounds
                .getHeight());
        return new Point3f(xF, yF, 0);
    }

    protected Node buildTriangle(BufferedImage textureImg,
            Vector<vertex> vertices) {
        Rectangle2D shapeBounds = getBounds(vertices);
        IndexedTriangleArray tri = new IndexedTriangleArray(3,
                IndexedQuadArray.COORDINATES
                        | IndexedQuadArray.TEXTURE_COORDINATE_2, 1, new int[] {
                        0, 0 }, 3);
        Point3f[] triCoords = {
                getCoordInCanvasSpace(shapeBounds, vertices.get(0)),
                getCoordInCanvasSpace(shapeBounds, vertices.get(1)),
                getCoordInCanvasSpace(shapeBounds, vertices.get(2)) };
        TexCoord2f[] textureCoordinates = {
                new TexCoord2f(vertices.get(0).textureCoords.x, 1 - vertices
                        .get(0).textureCoords.y),
                new TexCoord2f(vertices.get(1).textureCoords.x, 1 - vertices
                        .get(1).textureCoords.y),
                new TexCoord2f(vertices.get(2).textureCoords.x, 1 - vertices
                        .get(2).textureCoords.y) };

        int coordIndices[] = { 0, 1, 2 };
        int texCoordIndices[] = { 0, 1, 2 };
        tri.setCoordinates(0, triCoords);
        tri.setCoordinateIndices(0, coordIndices);

        tri.setTextureCoordinates(0, 0, textureCoordinates);
        tri.setTextureCoordinateIndices(0, 0, texCoordIndices);
        return new Shape3D(tri, DefineAppearance(textureImg));
    }

    private void textureMapPoly(Graphics2D canvas, Vector<vertex> vertices) {
        BufferedImage img = null;
        try {
            myCanvas3D.renderOffScreenBuffer();
            myCanvas3D.waitForOffScreenRendering();
            myCanvas3D.repaint();
            img = myCanvas3D.getOffScreenBuffer().getImage();
            // ImageIO.write(img,"png",new
            // File("C:/workspace/screenCapture/cached.png"));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        Dimension d = new Dimension((int) Math.ceil(img.getWidth()
                / (float) antiAliasScale), (int) Math.ceil(img.getHeight()
                / (float) antiAliasScale));
        Image scaledImg = img.getScaledInstance(d.width, d.height,
                BufferedImage.SCALE_SMOOTH);

        Rectangle2D bounds = getBounds(vertices);
        canvas.drawImage(scaledImg, (int) Math.round(bounds.getX()), (int) Math
                .round(bounds.getY()), null);
    }

    public void clearBufferedImage(BufferedImage img) {
        Graphics2D g2 = img.createGraphics();
        g2.setBackground(new Color(0, 0, 0, 0));
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
        g2.fillRect(0, 0, img.getWidth(), img.getHeight());
        g2.dispose();
    }

    private void makeOffscreen(Vector<vertex> vertices) {
        Rectangle2D bounds = getBounds(vertices);
        bImage = new BufferedImage((int) Math.round(bounds.getWidth()
                * antiAliasScale), (int) Math.round(bounds.getHeight()
                * antiAliasScale), BufferedImage.TYPE_INT_ARGB);
        ImageComponent2D buffer = new ImageComponent2D(
                ImageComponent2D.FORMAT_RGBA, bImage, true, true);
        buffer.setCapability(ImageComponent2D.ALLOW_IMAGE_READ);
        buffer.setCapability(ImageComponent2D.ALLOW_IMAGE_WRITE);
        myCanvas3D.setOffScreenBuffer(buffer);
        myCanvas3D.getScreen3D().setSize(bImage.getWidth(), bImage.getHeight());
        myCanvas3D.getScreen3D().setPhysicalScreenWidth(1);
        myCanvas3D.getScreen3D().setPhysicalScreenHeight(1);
    }
}

Comments
Add New Search
ugg boots  - ugg boots     |2010-01-30 23:32 MST
ugg outlet
uggs outlet
uggs outlet stores
ugg outlet store
cheap ugg boots
ugg on sale
uggs on sale
uggs on sale
air max
UGG Classic Short Boots
UGG Classic Cardy Boots
UGG Bailey Button Boots
UGG Fluff Flip Flop
UGG Classic
UGG Sandals
UGG Coquette Sandals
Ugg Dakota sandals
Ugg Stripe Cable Knit
CloD   |2010-02-01 23:37 MST
ooo... o-k-k-
liping  - wow gold   |2010-02-20 20:10 MST
Edwards has turnedwow gold the head of many progressivewow gold because hecheap wow gold actually talksbuy wow gold policy, butbuy cheap wow gold he’s starring in a well-knownbuy wow gold cheap role. Lackingwow gold cheap the party cheapest wow gold evermachine backingtbc gold Clinton, and the mediawow gold guide hagiography illuminating wow gold cheapObama, Edwards packages gold for wowhimself wow gold pricesas an issues wow gold hacksman, which ismake wow gold the role Jerry Brown wow power levelingfilled in the 1992wow powerleveling race and Howardwow cd key Dean in 2004.
liping   |2010-02-20 20:13 MST
Edwards has turnedwow gold the head of many progressivewow gold because hecheap wow gold actually talksbuy wow gold policy, butbuy cheap wow gold he’s starring in a well-knownbuy wow gold cheap role. Lackingwow gold cheap the party cheapest wow gold evermachine backingtbc gold Clinton, and the mediawow gold guide hagiography illuminating wow gold cheapObama, Edwards packages gold for wowhimself wow gold pricesas an issues wow gold hacksman, which ismake wow gold the role Jerry Brown wow power levelingfilled in the 1992wow powerleveling race and Howardwow cd key Dean in 2004.
liping   |2010-02-20 20:15 MST
Those are strongwow leveling words, but if Edwards world of warcraft levelingsomehowdfo gold does dfo powerlevelingmanage to get daoc goldthe nominationcamelot gold — mainly darkfall goldbecause the darkfall power levelingparty bosses cheap ffxi gilquake atffxi cheap gil the thought ffxi power levelingof either a runescape goldwoman or Black runescape moneyman headingrs2 power leveling up the ticketmaple Story mesos — he will startMaple Story meso singing the virtuesmaple story power leveling of the free market.EverQuest 2 gold Soeq2 plat far,EQ2 power leveling issuescity of heroes influence candidatescoh influence have notcity of heroes power level been city of villains infamynominated COV infamyin the city of villains power levelingpost-Watergatemetin2 gold erametin2 power level. Theyaion gold can aion power levelingcontend eve online iskbecauseeve isk they guild warsgenerate a guild wars armorgroundswell guild wars power levelingof runescape moneysupport, runescape goldbut rs...
liping   |2010-02-20 20:17 MST
On the Republicanwow cd key side,world of warcraft cd key the wow goldfieldwow power level isdfo gold more cheap dfo goldopen,daoc gold but camelot goldall buy darkfallthe darkfall goldcandidatesffxi gil arecheap ffxi gil lunatics.ffxi cd key Almostrunescape gold without runescape moneyexceptionmaple Story mesos they Maple Story mesocompeteEverQuest 2 gold to show whoeq2 plat hates eq2 cd keyimmigrants thecity of heroes influencemost, coh influencewhocity of heroes cd key willcity of villains influenceban cov influenceabortion city of villains cd keythe fastestmetin2 gold, who metin2 moneywill bomb eve online iskIran the eve iskfiercest,aion gold whoaion cd key will gw goldwaterboarguild wars gold the guild wars cd keymost pc games cd keyterrorists and vanguard cd keywho star wars galaxies cd keywill stay lineage2 cd keythe age of conan cd keycoursewarhammer online cd key in hellgate london cd keyIraq thetabula rasa cd key longest.age of conan cd key For warhammer online cd keycandi...
liping   |2010-02-20 20:18 MST
Among Democrats, talkingwow gold politics meanswow buy gold having to cheap wow goldaddress howwow gold sale corporationsbuying wow gold and wow power levelthe upper wow cd keyclass — theworld of warcraft cd key oneswow leveling who world of warcraft levelingfund Daoc Goldpresidential Camelot Goldcampaigns DFO Goldplunder Dungeon Fighter Goldthe Darkfall Goldgovernment. In Cheap Darkfall Goldone final fantasy xi giltelevision ad, final fantasy 11 cheap gilEdwards ffxi gilsays,cheap ffxi gil “We ffxi cheap gildon’t ffxi cd keyhave ffxi power levelinguniversalRunescape Money healthRS2 Gold carers2 power leveling becauseMaple Story Mesos ofMaple Story meso drugmaplestory Mesos companies,maplestory Meso insurancemaple mesos companies maple story power levelingandEverQuest 2 gold their eq2 platlobbyistseverquest 2 cd key in EQ2 power levelingWashington,coh influence D.C.” Incity of heroes influence anothercity of heroes cd key, he city of heroes power levelstates, COV Influence“Do
ed hardy  - ed hardy     |2010-02-21 20:36 MST
buy ed hardy
ed hardy for sale

nike max
sale air max

basketball shoes
all star jersey


ed hardy shoes
ed hardy socks
ed hardy sunglasses
ed hardy ties
ed hardy wallets/ luggage
ed hardy watches

Max 95
Women Air Max Ltd

Kobe Bryant
Jason Frederick Kidd
Steve Nash
ed-hardy  - http://www.ed-hardy.cc/     |2010-03-01 00:51 MST
Ed Hardy clothing often use some embroidery, washing, splash-ink and other
techniques, to create a feeling of decadence erosion, don ed hardyand then combined in the master reverberates from the Eagle, tiger
skeletons,nike shoes demons, daggers, and nudes, etc. tattoo, producing a series of Clothes.
Another strong oriental taste of the carp,ed hardya dragon and the tiger totem, the tiger dogs and squirrels are a relatively
new breed of cartoon creation.
ed hardy on sale  - http://www.ed-hardy.cc/ sale ed hardy     |2010-03-02 20:09 MST
sale ed hardy
ed hardy on sale
nike max
nike air max
air max shoes
nike basketball shoes
nba basketball jersey


ed hardy t-shirts
ed hardy woman shoes
ed hardy bags purses
ed hardy underwear loungewear
ed hardy women accessories
ed hardy woman caps
ed hardy woman jewelry
ed hardy woman scarves

Max Ltd
Max Tn

Tim Duncan
Allen Iverson
Penny Hardaway
air jordan  - air jordan     |2010-03-06 21:02 MST
air jordan
gucci handbags
air max shoes

nike air jordan
nike air max
nike shox
Air Force 1
Football Jerseys
Gucci shoes
Mlb Replica Jerseys
Nba Jerseys Adidas
Reebok Nfl Jerseys
Reebok Nhl Jerseys
Supra Shoes
Write comment
Name:
Email:
 
Website:
Title:
UBBCode:
[b] [i] [u] [url] [quote] [code] [img] 
 
 
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch:
:(:shock::X:side::):P:unsure::woohoo::huh::whistle:;):s
 
Please input the anti-spam code that you can read in the image.

3.26 Copyright (C) 2008 Compojoom.com / Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved."