// // CLASS // ExSpotLight - illustrate use of spot lights // // LESSON // Add a SpotLight node to illuminate a scene. // // SEE ALSO // ExAmbientLight // ExDirectionalLight // ExPointLight // // AUTHOR // David R. Nadeau / San Diego Supercomputer Center // import java.awt.*; import java.awt.event.*; import javax.media.j3d.*; import javax.vecmath.*; public class ExSpotLight extends Example { //-------------------------------------------------------------- // 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 ); } }