OK, I've composed a small program demonstrating the problems I'm
seeing. It has a sphere and a label both at the same position.
> 1) Rasters ignoring front and back clipping planes.
Zoom way in, zoom way out. The sphere disappears, the label
never does.
> 2) Rasters ignoring setDepthBufferEnable and always doing depth
> buffer compare.
This demo isn't showing this. Justin suggested that maybe it's a
rendering order issue. When alwaysOnTop is true (depth buffering
is disabled) the raster is always either entirely in front of
the sphere or entirely behind it. Justin suggested I use
OrderedGroup to make sure my rasters are rendered last.
OrderedGroup may be the only way to do what I need to do.
Since I can't control the depth of my rasters, I can use
OrderedGroup to render my objects in a specific order so
that objects in front of the scene obscure labels behind
them. Any comments on this? Is the use of OrderedGroup
going to slow me down? I guess I'll find out soon enough. . .
> 3) A raster and another Shape transformed by the same transforms
> end up at different depths.
Use the menu to turn off alwaysOnTop. Zoom in and notice how the
sphere comes right through the label. The label and the sphere
are at the same position, so the label should always be in the
center of the sphere. Depending on the position of the sphere,
the label moves relative to it in Z.
> 4) Documentation unclear about the meaning of a Raster's position.
> (Perhaps more bugs here too, depending on the meaning.)
I'm not setting the raster's position in this example program, for
simplicity.
> 5) CLIP_IMAGE causes flickering
Move the sphere over to the left so the left side of the raster is
clipped. Now spin the sphere and watch the label flicker.
> 6) Sign problem with setDstOffset
This may not be a bug, but you may want to document it because it's
not clear to me why positive X and Y offsets move the raster up and
to the left.
-Paul
/*****************************************************************************
* J3D.org Copyright (c) 2000
* Java Source
*
* This source is licensed under the GNU LGPL v2.1
* Please read http://www.gnu.org/copyleft/lgpl.html for more information
*
****************************************************************************/
package org.j3d.geom;
// Standard imports
import javax.media.j3d.*;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.vecmath.Point3f;
// Application specific imports
/**
* A text label for labelling objects on screen that uses a Java 3D Raster
* to produce the overlay effect.
* <p>
*
* If the label text is null, then no label will be displayed. All of the
* setup will be done, but no raster will be created.
* <p>
*
* The text label can come in a number of flavours depending on how you
* configure it through the constructors. You may build a label that is
* only static, always fixed size regardless of text length, and/or may
* be hidden from other objects v always on the top. Once configured to one
* of these versions, it cannot be changed.
* <p>
*
* If running dynamic text, the internal images will only resize to a
* larger size. That is, if a new string comes in that is smaller than
* the original string, the image will stay the larger length than the
* original. The idea is to reduce the amount of garbage generated. However,
* in some instances this may not produce acceptable visual behaviour,
* so the crop() method is introduced that will force the image size to
* be reduced to the smallest possible size for the next time a string
* is set. There are also variants on the setText() methods to do this
* as well with a flag. Cropping is independent of the fixedSize flag but is
* still subject to the dynamic flag on the constructor.
*
* @author Justin Couch
* @version $Revision: 2.0 $
*/
public class RasterTextLabel extends Shape3D
{
/** Message when the text label is not setup for dynamic changes */
private static final String CANT_CHANGE_MSG =
"Attempting to make a change to a label that was not originally " +
"configured to be dynamic";
/** The clear colour used to clear the background of the image */
private static final Color CLEAR_COLOR = new Color(0, 0, 0, 0);
/** The inset between the text and the border if one is required. */
private static final int BORDER_INSETS = 2;
/** The current color of the text */
private Color textColor;
/** The current color of the border. Null if not in use */
private Color borderColor;
/** The background color of the image */
private Color backgroundColor;
/** The font of the label. Null if using system default */
private Font labelFont;
/** The raster object that we put the information in */
private Raster raster;
/** The image component that holds the image used by the raster */
private ImageComponent2D component;
/**
* Flag to say if the implementation should resize the underlying label
* for each text update or not.
*/
private boolean adjustImageSize;
/** Flag indicating if this is a dynamic label */
private boolean isDynamic;
/** Underlying image used. Only set if this is a dynamic label */
private BufferedImage textImage;
/** The current image width */
private int imageWidth;
/** The current image height */
private int imageHeight;
/**
* Create a new blank label with no text. It is located at the origin. It
* is assumed to be dynamic and always on top.
*/
public RasterTextLabel()
{
this(null, null, true, true, 0, 0, 0, null, null);
}
/**
* Create a new blank label with the given text located at the origin.
* If the text color is not specified, white is used and the code assumes
* static text, it will always be on top.
*
* @param label The string to use on the label
* @param col The text color to be drawn in
*/
public RasterTextLabel(String label, Color col)
{
this(label, col, false, true, 0, 0, 0, null, null);
}
/**
* Create a new blank label with the given text located at the origin.
* If the text color is not specified, white is used and the code assumes
* static text, it will always be on top.
*
* @param label The string to use on the label
* @param col The text color to be drawn in
* @param alwaysOnTop true if this should never be obscured by content
*/
public RasterTextLabel(String label, Color col, boolean alwaysOnTop)
{
this(label, col, alwaysOnTop, false, 0, 0, 0, null, null);
}
/**
* Create a new blank label with the given text located at the origin.
* If the text color is not specified, white is used.
*
* @param label The string to use on the label
* @param col The text color to be drawn in
* @param dynamic True if this will change text over time
* @param alwaysOnTop true if this should never be obscured by content
*/
public RasterTextLabel(String label,
Color col,
boolean alwaysOnTop,
boolean dynamic)
{
this(label, col, alwaysOnTop, dynamic, 0, 0, 0, null, null);
}
/**
* Create a new blank label with the given text located at a specific
* point in 3D world coordinates. The code assumes a static label.
*
* @param label The string to use on the label
* @param col The text color to be drawn in
* @param alwaysOnTop true if this should never be obscured by content
* @param x The x world coordinate to place the label
* @param y The y world coordinate to place the label
* @param z The z world coordinate to place the label
*/
public RasterTextLabel(String label,
Color col,
boolean alwaysOnTop,
float x,
float y,
float z)
{
this(label, col, alwaysOnTop, false, x, y, z, null, null);
}
/**
* Create a new blank label with the given text located at a specific
* point in 3D world coordinates.
*
* @param label The string to use on the label
* @param col The text color to be drawn in
* @param x The x world coordinate to place the label
* @param y The y world coordinate to place the label
* @param z The z world coordinate to place the label
* @param alwaysOnTop true if this should never be obscured by content
* @param dynamic True if this will change text over time
*/
public RasterTextLabel(String label,
Color col,
boolean alwaysOnTop,
boolean dynamic,
float x,
float y,
float z)
{
this(label, col, alwaysOnTop, dynamic, x, y, z, null, null);
}
/**
* Create a new blank label with the given text located at a specific
* point in 3D world coordinates and an option to show a border and
* selected font. If the border color is specified, it will show a 1
* pixel wide border in that color. If no font is defined, the system
* default font will be used.
*
* @param label The string to use on the label
* @param col The text color to be drawn in
* @param x The x world coordinate to place the label
* @param y The y world coordinate to place the label
* @param z The z world coordinate to place the label
* @param border The color to use for the border or null for none
* @param font The font to draw the string in or null for default
* @param dynamic True if this will change text over time
*/
public RasterTextLabel(String label,
Color col,
boolean alwaysOnTop,
boolean dynamic,
float x,
float y,
float z,
Color border,
Font font)
{
adjustImageSize = false;
textColor = (col != null) ? col : Color.white;
borderColor = border;
labelFont = font;
isDynamic = dynamic;
Appearance app = new Appearance();
RenderingAttributes ra = new RenderingAttributes();
ra.setAlphaTestFunction(RenderingAttributes.GREATER);
if(alwaysOnTop)
{
ra.setDepthBufferEnable(false);
ra.setDepthBufferWriteEnable(false);
}
app.setRenderingAttributes(ra);
setAppearance(app);
// create a disposable 1x1 image so that we can fetch the font
// metrics associated with the font and text label. This will allow
// us to determine the real image size. This is kludgy, but I can't
// think of a better way of doing it!
BufferedImage tmp_img =
new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics graphics = tmp_img.getGraphics();
FontMetrics fm;
if(labelFont == null)
fm = graphics.getFontMetrics();
else
fm = graphics.getFontMetrics(labelFont);
int width = 0;
int height = 0;
if(label == null)
{
// No label? Create an empty (default) raster object then. This
// should result in nothing being rendered on screen, but leaves
// us a placeholder for later.
raster = new Raster();
raster.setPosition(new Point3f(x, y, z));
}
else
{
// now we have the metrics, let's work out how big the label is!
Rectangle2D dimensions = fm.getStringBounds(label, graphics);
graphics.dispose();
tmp_img.flush();
tmp_img = null;
width = (int)dimensions.getWidth();
height = (int)dimensions.getHeight();
int ascent = fm.getMaxAscent();
if(border != null)
{
width += BORDER_INSETS * 2 + 2; // one pixel border * 2
height += BORDER_INSETS * 2 + 2;
}
textImage = new BufferedImage(width,
height,
BufferedImage.TYPE_INT_ARGB);
graphics = textImage.getGraphics();
renderImage(graphics, label, width, height, ascent);
graphics.dispose();
component = new ImageComponent2D(ImageComponent2D.FORMAT_RGBA,
textImage);
raster = new Raster(new Point3f(x, y, z),
Raster.RASTER_COLOR,
0,
0,
width,
height,
component,
null);
// clear the reference if not dynamic
if(!dynamic)
{
component = null;
textImage = null;
}
}
if(dynamic)
{
component.setCapability(ImageComponent2D.ALLOW_IMAGE_WRITE);
raster.setCapability(Raster.ALLOW_SIZE_WRITE);
raster.setCapability(Raster.ALLOW_IMAGE_WRITE);
raster.clearCapabilityIsFrequent(Raster.ALLOW_IMAGE_WRITE);
}
setGeometry(raster);
}
/**
* Set the label string that is to be rendered. This maintains the
* current text color. If this was not set up to be a dynamic image, an
* exception is thrown.
*
* @param text The string to be rendered
* @throws IllegalStateException The label was not set up to be dynamic
* in the constructor
*/
public void setText(String text) throws IllegalStateException
{
if(!isDynamic)
throw new IllegalStateException(CANT_CHANGE_MSG);
updateText(text);
}
/**
* Set the label string that is to be rendered with the option of croping
* it to the length of the string. This maintains the current text color.
* If this was not set up to be a dynamic image, an exception is thrown.
*
* @param text The string to be rendered
* @param crop true to crop the underlying raster
* @throws IllegalStateException The label was not set up to be dynamic
* in the constructor
*/
public void setText(String text, boolean crop) throws IllegalStateException
{
if(!isDynamic)
throw new IllegalStateException(CANT_CHANGE_MSG);
if(crop)
textImage = null;
updateText(text);
}
/**
* Set the label string that is to be rendered and changes the color
* to the new value. If this was not set up to be a dynamic image, an
* exception is thrown.
*
* @param text The string to be rendered
* @param col The new color to be used or null for the default (white)
* @throws IllegalStateException The label was not set up to be dynamic
* in the constructor
*/
public void setText(String text, Color col) throws IllegalStateException
{
if(!isDynamic)
throw new IllegalStateException(CANT_CHANGE_MSG);
textColor = (col != null) ? col : Color.white;
updateText(text);
}
/**
* Set the label string that is to be rendered and changes the color
* to the new value. If this was not set up to be a dynamic image, an
* exception is thrown.
*
* @param text The string to be rendered
* @param col The new color to be used or null for the default (white)
* @param crop true to crop the underlying raster
* @throws IllegalStateException The label was not set up to be dynamic
* in the constructor
*/
public void setText(String text, Color col, boolean crop)
throws IllegalStateException
{
if(!isDynamic)
throw new IllegalStateException(CANT_CHANGE_MSG);
if(crop)
textImage = null;
textColor = (col != null) ? col : Color.white;
updateText(text);
}
/**
* Set the condition of whether the implementation should resize the
* canvas after each new label is set or just stick to a fixed size
* canvas. A fixed size label is useful when you are making fast updates
* such as a counter. When this is called, the label will not be resized
* from it's current dimensions. This may be changed dynamically and will
* only take effect next time a text string is set and the size is based
* on the biggest image used to date, not on the next string that is
* set.
*
* @param fixed true if the label size should remain fixed
* @throws IllegalStateException The label was not set up to be dynamic
* in the constructor
*/
public void fixSize(boolean fixed)
throws IllegalStateException
{
if(!isDynamic)
throw new IllegalStateException(CANT_CHANGE_MSG);
adjustImageSize = fixed;
}
/**
* Crop the image used for the raster to the length of the string the next
* time a string is set. This will not crop the current string, only the
* next one.
*
* @throws IllegalStateException The label was not set up to be dynamic
* in the constructor
*/
public void crop()
throws IllegalStateException
{
if(!isDynamic)
throw new IllegalStateException(CANT_CHANGE_MSG);
// easiest way to force a rebuild is to remove the textImage reference
// completely and treat it like a new image with no text set.
textImage = null;
}
/**
* Update the raster to display the new text.
*
* @param label The new text string to draw
* @param fontChanged true if the font changed
*/
private void updateText(String label)
{
// If we have no text to display, just set the size to display
// nothing and leave immediately.
if(label == null)
{
raster.setSize(0, 0);
return;
}
int width = 0;
int height = 0;
FontMetrics metrics = null;
Graphics graphics = null;
// Work on the null image first because adjustSize may also be set
// but would crash with a null source image.
if(textImage == null)
{
BufferedImage tmp_img =
new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
graphics = tmp_img.getGraphics();
if(labelFont == null)
metrics = graphics.getFontMetrics();
else
metrics = graphics.getFontMetrics(labelFont);
Rectangle2D dimensions = metrics.getStringBounds(label, graphics);
graphics.dispose();
tmp_img.flush();
width = (int)dimensions.getWidth();
height = (int)dimensions.getHeight();
if(borderColor != null)
{
width += BORDER_INSETS * 2 + 2; // one pixel border * 2
height += BORDER_INSETS * 2 + 2;
}
textImage = new BufferedImage(width,
height,
BufferedImage.TYPE_INT_ARGB);
graphics = textImage.createGraphics();
renderImage(graphics, label, width, height, metrics.getAscent());
component = new ImageComponent2D(ImageComponent2D.FORMAT_RGBA,
textImage);
component.setCapability(ImageComponent2D.ALLOW_IMAGE_WRITE);
raster.setSize(width, height);
raster.setImage(component);
}
else if(adjustImageSize)
{
// recalc size and compare against current.
graphics = textImage.getGraphics();
if(labelFont == null)
metrics = graphics.getFontMetrics();
else
metrics = graphics.getFontMetrics(labelFont);
Rectangle2D dimensions = metrics.getStringBounds(label, graphics);
width = (int)dimensions.getWidth();
height = (int)dimensions.getHeight();
if(borderColor != null)
{
width += BORDER_INSETS * 2 + 2; // one pixel border * 2
height += BORDER_INSETS * 2 + 2;
}
// So now we know the required size. Is it bigger than the current
// image size? Resize if new size if bigger than the old.
if((width > textImage.getWidth(null)) ||
(height > textImage.getHeight(null)))
{
textImage.flush();
textImage = new BufferedImage(width,
height,
BufferedImage.TYPE_INT_ARGB);
graphics = textImage.createGraphics();
// Probably superfluous, but in for correctness.
if(labelFont == null)
metrics = graphics.getFontMetrics();
else
metrics = graphics.getFontMetrics(labelFont);
renderImage(graphics, label, width, height, metrics.getAscent());
component = new ImageComponent2D(ImageComponent2D.FORMAT_RGBA,
textImage);
component.setCapability(ImageComponent2D.ALLOW_IMAGE_WRITE);
raster.setSize(width, height);
raster.setImage(component);
}
else
{
renderImage(graphics, label, width, height, metrics.getAscent());
component.set(textImage);
raster.setSize(width, height);
}
}
else
{
// fixed size
graphics = textImage.createGraphics();
if(labelFont == null)
metrics = graphics.getFontMetrics();
else
metrics = graphics.getFontMetrics(labelFont);
Rectangle2D dimensions = metrics.getStringBounds(label, graphics);
width = (int)dimensions.getWidth();
height = (int)dimensions.getHeight();
if(borderColor != null)
{
width += BORDER_INSETS * 2 + 2; // one pixel border * 2
height += BORDER_INSETS * 2 + 2;
}
renderImage(graphics, label, width, height, metrics.getAscent());
component.set(textImage);
raster.setSize(width, height);
}
graphics.dispose();
}
/**
* Convenience method to render the image given the font information. When
* this method exits, it will have changed the global imageWidth and
* imageHeight variables to the given width and height values.
*
* @param graphics The graphics context for textImage
* @param label The string to render
* @param width The width of the image drawn to
* @param height The height of the image drawn to
* @param ascent The ascent of the font in use
*/
private void renderImage(Graphics graphics,
String label,
int width,
int height,
int ascent)
{
Graphics2D g = (Graphics2D)graphics;
g.setComposite(AlphaComposite.Src);
g.setColor(CLEAR_COLOR);
g.fillRect(0, 0, imageWidth, imageHeight);
if(borderColor != null)
{
g.setColor(borderColor);
g.drawRect(0, 0, width - 1, height - 1);
g.setColor(textColor);
g.setFont(labelFont);
g.drawString(label,
BORDER_INSETS + 1,
ascent + BORDER_INSETS + 1);
}
else
{
g.setColor(textColor);
g.setFont(labelFont);
g.drawString(label, 0, ascent);
}
imageWidth = width;
imageHeight = height;
}
}
import java.awt.*;
import java.awt.event.*;
import java.awt.Rectangle;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import javax.media.j3d.*;
import javax.vecmath.*;
import javax.swing.*;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.universe.PlatformGeometry;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.ViewingPlatform;
import com.sun.j3d.utils.image.TextureLoader;
import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.behaviors.keyboard.KeyNavigatorBehavior;
import com.sun.j3d.utils.applet.JMainFrame;
import org.j3d.geom.RasterTextLabel;
import org.j3d.geom.*;
public final class RasterBugs extends JApplet {
private static final Geometry sphereGeometry;
private KeyNavigatorBehavior keyBehavior;
private TransformGroup vptg;
private static final float MOVE_BACK = 2.0f;
private BranchGroup objRoot = null;
private static final Color3f BLACK = new Color3f(0f, 0f, 0f);
private static final Color3f WHITE = new Color3f(1f, 1f, 1f);
private static final Color3f GREEN = new Color3f(0.5f, 1f, 0.5f);
private SimpleUniverse u;
private PlatformGeometry pg;
private BoundingSphere bounds;
private static final Point3d D_ORIGIN = new Point3d(0, 0, 0);
private Canvas3D c3d;
private boolean alwaysOnTop = true;
private RenderingAttributes ra;
static {
final int SPHERE_TESSELATION = 20;
Sphere s =
new Sphere(1.0f, Sphere.GENERATE_NORMALS, SPHERE_TESSELATION, null);
sphereGeometry = s.getShape().getGeometry();
} // End of static initializer
/**
* Set up keyboard navigation.
*/
private void addKeyBehavior(BranchGroup b) {
// Set up keyboard navigation
keyBehavior = new KeyNavigatorBehavior(
u.getViewingPlatform().getViewPlatformTransform());
keyBehavior.setSchedulingBounds(bounds);
// Add behavior to scenegraph
b.addChild(keyBehavior);
} // End of addKeyBehavior
/**
* Initialize and return the Canvas3D we'll be using for rendering.
*/
public void init() {
bounds = new BoundingSphere(D_ORIGIN, Double.POSITIVE_INFINITY);
// Use utility to get the right graphics destination
GraphicsConfiguration config =
SimpleUniverse.getPreferredConfiguration();
// Create a canvas on the device
c3d = new Canvas3D(config);
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
contentPane.add(c3d, "Center");
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
JMenu viewMenu = new JMenu("View");
menuBar.add(viewMenu);
JMenuItem inFrontItem = new JMenuItem("not alwaysOnTop");
viewMenu.add(inFrontItem);
inFrontItem.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuItem inFrontItem = (JMenuItem)e.getSource();
alwaysOnTop = !alwaysOnTop;
if (alwaysOnTop) {
inFrontItem.setText("not alwaysOnTop");
ra.setDepthBufferEnable(false);
ra.setDepthBufferWriteEnable(false);
} else {
inFrontItem.setText("alwaysOnTop");
ra.setDepthBufferEnable(true);
ra.setDepthBufferWriteEnable(true);
}
}
}
);
// Use utility to create default universe
u = new SimpleUniverse(c3d);
// Set the viewpoint back from the origin and point toward the origin
ViewingPlatform vp = u.getViewingPlatform();
vp.setNominalViewingTransform();
vptg = vp.getViewPlatformTransform();
Transform3D vpt = new Transform3D();
vptg.getTransform(vpt);
Transform3D translate = new Transform3D();
translate.setTranslation(new Vector3f(0f,0f,MOVE_BACK));
vpt.mul(translate);
vptg.setTransform(vpt);
// Allow mouse manipulation
OrbitBehavior orbit = new OrbitBehavior(c3d, OrbitBehavior.REVERSE_ALL);
// Behavior is only enabled within its bounds
bounds = new BoundingSphere(D_ORIGIN, 100.0);
orbit.setSchedulingBounds(bounds);
// Attach behavior to ViewingPlatform
vp.setViewPlatformBehavior(orbit);
// Will add lights here so they are "headlights"
// that move with the viewer
pg = new PlatformGeometry();
// Set up the ambient light
Color3f ambientColor = new Color3f(0.1f, 0.1f, 0.1f);
AmbientLight ambientLightNode = new AmbientLight(ambientColor);
ambientLightNode.setInfluencingBounds(bounds);
pg.addChild(ambientLightNode);
// Set up the directional lights
Color3f light1Color = new Color3f(1.0f, 1.0f, 0.9f);
Vector3f light1Direction = new Vector3f(1.0f, 1.0f, 1.0f);
Color3f light2Color = new Color3f(1.0f, 1.0f, 1.0f);
Vector3f light2Direction = new Vector3f(-1.0f, -1.0f, -1.0f);
DirectionalLight light1
= new DirectionalLight(light1Color, light1Direction);
light1.setInfluencingBounds(bounds);
pg.addChild(light1);
DirectionalLight light2
= new DirectionalLight(light2Color, light2Direction);
light2.setInfluencingBounds(bounds);
pg.addChild(light2);
vp.setPlatformGeometry(pg);
objRoot = new BranchGroup();
objRoot.setCapability(objRoot.ALLOW_DETACH);
objRoot.setCapability(objRoot.ALLOW_CHILDREN_EXTEND);
objRoot.setCapability(objRoot.ALLOW_CHILDREN_WRITE);
addKeyBehavior(objRoot);
objRoot.compile();
u.addBranchGraph(objRoot);
addNode();
} // End of init
/**
* Add a node to the graph.
*/
public void addNode()
{
// Each node is connected by a BranchGroup (to allow it to
// be removed)
BranchGroup root = new BranchGroup();
// Transform Group to hold position transform
TransformGroup posTg = new TransformGroup();
Transform3D pos = new Transform3D();
Vector3f translate = new Vector3f(0.2f,0f,0f);
pos.setTranslation(translate);
posTg.setTransform(pos);
root.addChild(posTg);
// TransformGroup to hold scale transform
TransformGroup scaleTg = new TransformGroup();
Transform3D scale = new Transform3D();
scale.setScale(0.2);
scaleTg.setTransform(scale);
posTg.addChild(scaleTg);
// Set appearance of node
ra = new RenderingAttributes();
Material mat = new Material(BLACK, BLACK, GREEN, WHITE, 128.0f);
Appearance a = new Appearance();
a.setMaterial(mat);
a.setRenderingAttributes(ra);
Shape3D shape = new Shape3D(sphereGeometry, a);
scaleTg.addChild(shape);
BranchGroup bg = new BranchGroup();
RasterTextLabel rtl = new RasterTextLabel(
"RasterTextLabel", Color.WHITE, alwaysOnTop, false, 0f, 0f, 0f);
Raster labelText = (Raster)rtl.getGeometry();
labelText.setClipMode(Raster.CLIP_IMAGE);
Appearance ap = rtl.getAppearance();
ra = ap.getRenderingAttributes();
ra.setCapability(RenderingAttributes.ALLOW_DEPTH_ENABLE_WRITE);
Dimension dim = new Dimension();
labelText.getSize(dim);
labelText.setDstOffset(dim.width / 2, dim.height / 2);
posTg.addChild(rtl);
root.compile();
objRoot.addChild(root);
} // End of addNode
public static void main(String[] args) {
new JMainFrame(new RasterBugs(args), 500, 500);
}
public RasterBugs(String[] args)
{
}
public RasterBugs()
{
}
} // End of file RenderEngine.java