Texture Mapping in Java 2D
Written by Rob Truxler
Thursday, 01 October 2009 16:28
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 atop-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 getscalled, I build a "polygon," which is a Vector of vertices (of the typeTextureMapper.Vertex). The polygon should be left open so that thefirst 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 work around 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 1can be used to use no anti-aliasing 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.
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.8578 |  |
| |
| 2 | 62.2 | 842.1781 |  |  |  |
| 4 | 74.8 | 1145.414 |  |  |  |
| 8 | 90.8 | 2729.284 |  |  |  |
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 jagged 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 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> 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> 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>();
}
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);
}
}