|
This Java tip illustrates the use of spot lights in Java 3D scenes.
import java.applet.Applet;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.CheckboxMenuItem;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Frame;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.util.Enumeration;
import java.util.EventListener;
import javax.media.j3d.Appearance;
import javax.media.j3d.Behavior;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.Group;
import javax.media.j3d.Light;
import javax.media.j3d.LineArray;
import javax.media.j3d.LineAttributes;
import javax.media.j3d.Material;
import javax.media.j3d.Shape3D;
import javax.media.j3d.SpotLight;
import javax.media.j3d.Switch;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.WakeupCriterion;
import javax.media.j3d.WakeupOnAWTEvent;
import javax.media.j3d.WakeupOnElapsedFrames;
import javax.media.j3d.WakeupOr;
import javax.vecmath.AxisAngle4f;
import javax.vecmath.Color3f;
import javax.vecmath.Matrix4d;
import javax.vecmath.Matrix4f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;
import com.sun.j3d.utils.geometry.Cone;
import com.sun.j3d.utils.geometry.Primitive;
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.Viewer;
import com.sun.j3d.utils.universe.ViewingPlatform;
public class ExSpotLight extends Java3DFrame {
//--------------------------------------------------------------
// SCENE CONTENT
//--------------------------------------------------------------
//
// Nodes (updated via menu)
//
private SpotLight light = null;
//
// Build scene
//
public Group buildScene() {
// Get the current color, position, attenuation,
// spread angle, and concentration
Color3f color = (Color3f) colors[currentColor].value;
Point3f pos = (Point3f) positions[currentPosition].value;
Vector3f dir = (Vector3f) directions[currentDirection].value;
Point3f atten = (Point3f) attenuations[currentAttenuation].value;
float spread = ((Double) spreads[currentSpread].value).floatValue();
float concen = ((Double) concentrations[currentConcentration].value)
.floatValue();
// Turn off the example headlight
setHeadlightEnable(false);
// Build the scene root
Group scene = new Group();
// BEGIN EXAMPLE TOPIC
// Create influencing bounds
BoundingSphere worldBounds = new BoundingSphere(new Point3d(0.0, 0.0,
0.0), // Center
1000.0); // Extent
// Set the light color and its influencing bounds
light = new SpotLight();
light.setEnable(lightOnOff);
light.setColor(color);
light.setPosition(pos);
light.setAttenuation(atten);
light.setDirection(dir);
light.setSpreadAngle(spread);
light.setConcentration(concen);
light.setCapability(SpotLight.ALLOW_STATE_WRITE);
light.setCapability(SpotLight.ALLOW_COLOR_WRITE);
light.setCapability(SpotLight.ALLOW_POSITION_WRITE);
light.setCapability(SpotLight.ALLOW_ATTENUATION_WRITE);
light.setCapability(SpotLight.ALLOW_DIRECTION_WRITE);
light.setCapability(SpotLight.ALLOW_SPREAD_ANGLE_WRITE);
light.setCapability(SpotLight.ALLOW_CONCENTRATION_WRITE);
light.setInfluencingBounds(worldBounds);
scene.addChild(light);
// END EXAMPLE TOPIC
// Build foreground geometry
scene.addChild(new SphereGroup());
// Add annotation arrows in a fan to show light ray directions,
// positions, and the spread angle
scene.addChild(buildArrows());
return scene;
}
//--------------------------------------------------------------
// FOREGROUND AND ANNOTATION CONTENT
//--------------------------------------------------------------
//
// Create a set of fans of annotation arrows initially pointing in
// the +X direction. Each fan in the set illustrates a different
// light spread angle listed in the user interface light control
// menu. Next, build two TransformGroups, one nested within
// the other, and place the fan within the innermost TransformGroup.
// The outer TransformGroup will be used to position the fan,
// and the inner one will be used to rotate the fan. The position
// and orientation are both selected via menu items. To do the
// position and orientation change, we compute a bunch of Transform3Ds
// for each menu choice position or orientation and save them.
// Later, when the user selects a new light direction or position,
// we poke the corresponding Transform3D into the appropriate
// TransformGroup, causing the arrows to change position or direction.
//
private TransformGroup arrowDirectionTransformGroup = null;
private Transform3D[] arrowDirectionTransforms = null;
private TransformGroup arrowPositionTransformGroup = null;
private Transform3D[] arrowPositionTransforms = null;
private Switch arrowSpreadAngleSwitch = null;
private Group buildArrows() {
// Create a switch group to hold the different arrow fan
// spread angle choices. Enable child choice writing.
arrowSpreadAngleSwitch = new Switch();
arrowSpreadAngleSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE);
// Create a set of arrow fans, one per spread angle
// shown on the menu.
AnnotationArrowFan af = null;
float spread = 0.0f;
for (int i = 0; i < spreads.length; i++) {
spread = ((Double) spreads[i].value).floatValue();
af = new AnnotationArrowFan(0.0f, 0.0f, 0.0f, // center position
2.5f, // arrow length
-spread, // start angle
spread, // end angle
5); // number of arrows
arrowSpreadAngleSwitch.addChild(af);
}
// Select the current fan.
arrowSpreadAngleSwitch.setWhichChild(currentSpread);
// Create an outer transform group used to change the fan
// position. Enable writing of its transform.
arrowPositionTransformGroup = new TransformGroup();
arrowPositionTransformGroup
.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
// Create a set of Transform3Ds for the different arrow positions.
arrowPositionTransforms = new Transform3D[positions.length];
Point3f pos;
Vector3f v = new Vector3f();
for (int i = 0; i < positions.length; i++) {
// Create a Transform3D, setting its translation.
arrowPositionTransforms[i] = new Transform3D();
pos = (Point3f) positions[i].value;
v.set(pos);
arrowPositionTransforms[i].setTranslation(v);
}
// Set the initial transform to be the current position
arrowPositionTransformGroup
.setTransform(arrowPositionTransforms[currentPosition]);
// Create an inner transform group surrounding the arrows,
// used to set the aim direction. Enable writing of its transform.
arrowDirectionTransformGroup = new TransformGroup();
arrowDirectionTransformGroup
.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
// Add the switch group to the direction-change transform group,
// and add the direction-change transform group to the
// position-change transform gorup.
arrowDirectionTransformGroup.addChild(arrowSpreadAngleSwitch);
arrowPositionTransformGroup.addChild(arrowDirectionTransformGroup);
// Create a set of Transform3Ds for the different
// arrow directions.
arrowDirectionTransforms = new Transform3D[directions.length];
Vector3f dir = new Vector3f();
Vector3f positiveX = new Vector3f(1.0f, 0.0f, 0.0f);
Vector3f axis = new Vector3f();
float angle;
float dot;
for (int i = 0; i < directions.length; i++) {
// Normalize the direction vector
dir.normalize((Vector3f) directions[i].value);
// Cross the direction vector with the arrow's
// +X aim direction to get a vector orthogonal
// to both. This is the rotation axis.
axis.cross(positiveX, dir);
if (axis.x == 0.0f && axis.y == 0.0f && axis.z == 0.0f) {
// New direction is parallel to current
// arrow direction. Default to a Y axis.
axis.y = 1.0f;
}
// Compute the angle between the direction and +X
// vectors, where:
//
// cos(angle) = (dir dot positiveX)
// -------------------------------
// (positiveX.length * dir.length)
//
// but since positiveX is normalized (as created
// above) and dir has been normalized, both have
// a length of 1. So, the angle between the
// vectors is:
//
// angle = arccos(dir dot positiveX)
dot = dir.dot(positiveX);
angle = (float) Math.acos(dot);
// Create a Transform3D, setting its rotation using
// an AxisAngle4f, which takes an XYZ rotation vector
// and an angle to rotate by around that vector.
arrowDirectionTransforms[i] = new Transform3D();
arrowDirectionTransforms[i].setRotation(new AxisAngle4f(axis.x,
axis.y, axis.z, angle));
}
// Set the initial transform to be the current aim direction.
arrowDirectionTransformGroup
.setTransform(arrowDirectionTransforms[currentDirection]);
return arrowPositionTransformGroup;
}
//--------------------------------------------------------------
// USER INTERFACE
//--------------------------------------------------------------
//
// Main
//
public static void main(String[] args) {
ExSpotLight ex = new ExSpotLight();
ex.initialize(args);
ex.buildUniverse();
ex.showFrame();
}
// On/off choices
private boolean lightOnOff = true;
private CheckboxMenuItem lightOnOffMenu;
// Color menu choices
private NameValue[] colors = { new NameValue("White", White),
new NameValue("Gray", Gray), new NameValue("Black", Black),
new NameValue("Red", Red), new NameValue("Yellow", Yellow),
new NameValue("Green", Green), new NameValue("Cyan", Cyan),
new NameValue("Blue", Blue), new NameValue("Magenta", Magenta), };
private int currentColor = 0;
private CheckboxMenu colorMenu = null;
// Position menu choices
private NameValue[] positions = { new NameValue("Origin", Origin),
new NameValue("+X", PlusX), new NameValue("-X", MinusX),
new NameValue("+Y", PlusY), new NameValue("-Y", MinusY),
new NameValue("+Z", PlusZ), new NameValue("-Z", MinusZ), };
private int currentPosition = 0;
private CheckboxMenu positionMenu = null;
// Attenuation menu choices
private NameValue[] attenuations = {
new NameValue("Constant", new Point3f(1.0f, 0.0f, 0.0f)),
new NameValue("Linear", new Point3f(0.0f, 1.0f, 0.0f)),
new NameValue("Quadratic", new Point3f(0.0f, 0.0f, 1.0f)), };
private int currentAttenuation = 0;
private CheckboxMenu attenuationMenu = null;
// Direction menu choices
private NameValue[] directions = { new NameValue("Positive X", PosX),
new NameValue("Negative X", NegX),
new NameValue("Positive Y", PosY),
new NameValue("Negative Y", NegY),
new NameValue("Positive Z", PosZ),
new NameValue("Negative Z", NegZ), };
private int currentDirection = 0;
private CheckboxMenu directionMenu = null;
// Spread angle choices
private NameValue[] spreads = {
new NameValue("22.5 degrees", new Double(Math.PI / 8.0)),
new NameValue("45.0 degrees", new Double(Math.PI / 4.0)),
new NameValue("90.0 degrees", new Double(Math.PI / 2.0)), };
private int currentSpread = 1;
private CheckboxMenu spreadMenu = null;
// Concentration choices
private NameValue[] concentrations = {
new NameValue("0.0", new Double(0.0)),
new NameValue("5.0", new Double(5.0)),
new NameValue("10.0", new Double(10.0)),
new NameValue("50.0", new Double(50.0)),
new NameValue("100.0", new Double(100.0)), };
private int currentConcentration = 0;
private CheckboxMenu concentrationMenu = null;
//
// Initialize the GUI (application and applet)
//
public void initialize(String[] args) {
// Initialize the window, menubar, etc.
super.initialize(args);
exampleFrame.setTitle("Java 3D Spot Light Example");
//
// Add a menubar menu to change node parameters
// Light on/off
// Color -->
// Position -->
// Direction -->
// Attenuation -->
// Spread Angle -->
// Concentration -->
//
Menu m = new Menu("SpotLight");
lightOnOffMenu = new CheckboxMenuItem("Light on/off");
lightOnOffMenu.addItemListener(this);
lightOnOffMenu.setState(lightOnOff);
m.add(lightOnOffMenu);
colorMenu = new CheckboxMenu("Color", colors, currentColor, this);
m.add(colorMenu);
positionMenu = new CheckboxMenu("Position", positions, currentPosition,
this);
m.add(positionMenu);
directionMenu = new CheckboxMenu("Direction", directions,
currentDirection, this);
m.add(directionMenu);
attenuationMenu = new CheckboxMenu("Attenuation", attenuations,
currentAttenuation, this);
m.add(attenuationMenu);
spreadMenu = new CheckboxMenu("Spread Angle", spreads, currentSpread,
this);
m.add(spreadMenu);
concentrationMenu = new CheckboxMenu("Concentration", concentrations,
currentConcentration, this);
m.add(concentrationMenu);
exampleMenuBar.add(m);
}
//
// Handle checkboxes and menu choices
//
public void checkboxChanged(CheckboxMenu menu, int check) {
if (menu == colorMenu) {
// Change the light color
currentColor = check;
Color3f color = (Color3f) colors[check].value;
light.setColor(color);
return;
}
if (menu == positionMenu) {
// Change the light position
currentPosition = check;
Point3f pos = (Point3f) positions[check].value;
light.setPosition(pos);
// Change the arrow group position
arrowPositionTransformGroup
.setTransform(arrowPositionTransforms[check]);
return;
}
if (menu == directionMenu) {
// Change the light direction
currentDirection = check;
Vector3f dir = (Vector3f) directions[check].value;
light.setDirection(dir);
// Change the arrow group direction
arrowDirectionTransformGroup
.setTransform(arrowDirectionTransforms[check]);
return;
}
if (menu == attenuationMenu) {
// Change the light attenuation
currentAttenuation = check;
Point3f atten = (Point3f) attenuations[check].value;
light.setAttenuation(atten);
return;
}
if (menu == spreadMenu) {
// Change the light spread angle
currentSpread = check;
arrowSpreadAngleSwitch.setWhichChild(check);
float spread = ((Double) spreads[check].value).floatValue();
light.setSpreadAngle(spread);
return;
}
if (menu == concentrationMenu) {
// Change the light concentration
currentConcentration = check;
float concen = ((Double) concentrations[check].value).floatValue();
light.setConcentration(concen);
return;
}
// Handle all other checkboxes
super.checkboxChanged(menu, check);
}
public void itemStateChanged(ItemEvent event) {
Object src = event.getSource();
if (src == lightOnOffMenu) {
// Turn the light on or off
lightOnOff = lightOnOffMenu.getState();
light.setEnable(lightOnOff);
return;
}
// Handle all other checkboxes
super.itemStateChanged(event);
}
}
//
//CLASS
//AnnotationArrowFan - A group of arrows in a fan
//
//DESCRIPTION
//This class creates one or more 3D, unlighted arrows arranged in a
//fan around the xyz position. Such arrow fans can be used to indicate
//point light directions, and so forth.
//
//The arrow fan is drawn in the XY plane, pointing right (middle arrow).
//The fan origin, arrow length, start and end angles, and number of
//arrows all may be controlled.
//
//SEE ALSO
//AnnotationArrow
//AnnotationArrowGroup
//
//AUTHOR
//David R. Nadeau / San Diego Supercomputer Center
//
//
class AnnotationArrowFan extends Group {
// 3D nodes
AnnotationArrow[] arrows;
// Constructors
public AnnotationArrowFan() {
// xyz length start/end angles count
this(0.0f, 0.0f, 0.0f, 1.0f, 1.571f, -1.571f, 5);
}
public AnnotationArrowFan(float x, float y, float z, float length,
float startAngle, float endAngle, int count) {
arrows = new AnnotationArrow[count];
float x2, y2;
float angle = startAngle;
float deltaAngle = (endAngle - startAngle) / (float) (count - 1);
for (int i = 0; i < count; i++) {
x2 = (float) (length * Math.cos(angle));
y2 = (float) (length * Math.sin(angle));
arrows[i] = new AnnotationArrow(x, y, z, x2, y2, z);
addChild(arrows[i]);
angle += deltaAngle;
}
}
}
//
//CLASS
//AnnotationArrowGroup - A group of parallel arrows
//
//DESCRIPTION
//This class creates one or more parallel 3D, unlighted arrows.
//Such arrow groups can be used to indicate directional light
//directions, and so forth.
//
//The arrow group is drawn in the XY plane, pointing right.
//The X start and end values, and the Y start and end values
//can be set, along with the count of the number of arrows to
//build.
//
//SEE ALSO
//AnnotationArrow
//AnnotationArrowFan
//
//AUTHOR
//David R. Nadeau / San Diego Supercomputer Center
//
//
class AnnotationArrowGroup extends Group {
// 3D nodes
AnnotationArrow[] arrows;
// Constructors
public AnnotationArrowGroup() {
// xStart xEnd yStart yEnd count
this(-1.0f, 1.0f, 1.0f, -1.0f, 3);
}
public AnnotationArrowGroup(float xStart, float xEnd, float yStart,
float yEnd, int count) {
arrows = new AnnotationArrow[count];
float y = yStart;
float deltaY = (yEnd - yStart) / (float) (count - 1);
for (int i = 0; i < count; i++) {
arrows[i] = new AnnotationArrow(xStart, y, 0.0f, xEnd, y, 0.0f);
addChild(arrows[i]);
y += deltaY;
}
}
}
//
//CLASS
//AnnotationArrow - 3D arrow used for annotation & diagrams
//
//DESCRIPTION
//This class creates a 3D, unlighted line between two 3D coordinates
//plus a cone-shaped arrow at the line's endpoint. The line's width
//and color can be controlled. The arrow head's width and length
//can be controlled.
//
//SEE ALSO
//AnnotationLine
//AnnotationAxes
//AnnotationArrowFan
//AnnotationArrowGroup
//
//AUTHOR
//David R. Nadeau / San Diego Supercomputer Center
//
class AnnotationArrow extends AnnotationLine {
// Parameters
private Color3f arrowColor = new Color3f(1.0f, 1.0f, 1.0f);
private float arrowRadius = 0.1f;
private float arrowLength = 0.20f;
private float lineWidth = 3.0f;
private int radialDivisions = 8;
private int sideDivisions = 1;
// 3D Nodes
private Cone arrowHead = null;
private Appearance arrowAppearance = null;
private TransformGroup arrowTrans = null;
private ColoringAttributes coloringAttributes = null;
//
// Construct a straight line
//
public AnnotationArrow(float x2, float y2, float z2) {
// origin to given coordinate
this(0.0f, 0.0f, 0.0f, x2, y2, z2);
}
public AnnotationArrow(float x, float y, float z, float x2, float y2,
float z2) {
super(x, y, z, x2, y2, z2);
setLineWidth(lineWidth);
// Compute the length and direction of the line
float deltaX = x2 - x;
float deltaY = y2 - y;
float deltaZ = z2 - z;
float theta = -(float) Math.atan2(deltaZ, deltaX);
float phi = (float) Math.atan2( | |