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 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.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 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);
}
}