[Date Prev][Date Next] [Chronological] [Thread] [Top]

Demo for screen locked renderer.



Hi everyone,

thanks for you patience in waiting for this email.

It took much longer than I had expected to prepare
the demo, and it also managed to give me a better
understanding of the problem.

I also managed to add a new shadow type
(ShadowScreenLockedSetTypeJ3D.java)
which screen-locks Sets.

Please find attached 3 files:
- ScreenLockedDemo.java
- ScreenLockedRendererJ3D.java
- ShadowScreenLockedSetTypeJ3D.java

You will need to place ScreenLockedRendererJ3D.java and
ShadowScreenLockedSetTypeJ3D.java in your visad/bom
directory, and compile them there.

Before running the  demo, please read the javadoc at the
top of ScreenLockedDemo.java.  There's a lot to read but
it will help you, and it also includes instructions on how
to run the tests.

To run the program,
 > javac ScreenLockedDemo.java
 > java ScreenLockedDemo

Look forward to hearing your thoughts on this.   I hope
the main javadoc makes sense to you.

Please let me know if you get stuck.

Best regards,
Jim.
---
Jim Koutsovasilis
Bureau of Meteorology, Australia
jimk@bom.gov.au

//
// ScreenLockedRendererJ3D.java
//

/*
VisAD system for interactive analysis and visualization of numerical
data.  Copyright (C) 1996 - 2002 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/

package visad.bom;

// Java
import java.awt.Font;
import java.rmi.RemoteException;
import javax.swing.JFrame;

// VisAD
import visad.ConstantMap;
import visad.DataDisplayLink;
import visad.DataReferenceImpl;
import visad.DelaunayCustom;
import visad.Display;
import visad.FieldImpl;
import visad.FunctionType;
import visad.GraphicsModeControl;
import visad.Gridded2DSet;
import visad.Irregular2DSet;
import visad.RealTupleType;
import visad.RealType;
import visad.Text;
import visad.TextControl;
import visad.TextType;
import visad.TupleType;
import visad.RealTupleType;
import visad.ScalarMap;
import visad.Set;
import visad.SetType;
import visad.ShadowType;
import visad.VisADException;
import visad.java3d.DefaultRendererJ3D;
import visad.java3d.DisplayImplJ3D;
import visad.java3d.DisplayRendererJ3D;
import visad.java3d.MouseBehaviorJ3D;
import visad.java3d.ProjectionControlJ3D;
import visad.java3d.ShadowFunctionTypeJ3D;
import visad.java3d.ShadowSetTypeJ3D;
import visad.java3d.ShadowTupleTypeJ3D;


/**
 * This renderer locks text to its initial position on the
 * screen.
 *
 * The render only works if you have a domain tuple of the form:
 * (latitude, longitude, text)
 * or a function type of the form:
 * ((latitude, longitude)->(text))
 */
public class ScreenLockedRendererJ3D extends DefaultRendererJ3D 
{

  /**
   * Default constructor.
   */
  public ScreenLockedRendererJ3D()
  {
    super();
  }


  /**
   * This is used for function types of the form:
   * ((latitude, longitude)->(text))
   */
  public ShadowType makeShadowFunctionType(FunctionType type, 
    DataDisplayLink link, ShadowType parent) 
    throws RemoteException, VisADException
  {
    return new ShadowScreenLockedFunctionTypeJ3D(type, link, parent);
  }


  /**
   * This is used for tuples of the form:
   * (latitude, longitude, text)
   */
  public ShadowType makeShadowTupleType(TupleType type, DataDisplayLink link,
    ShadowType parent) 
    throws RemoteException, VisADException 
  { 
    return new ShadowScreenLockedTupleTypeJ3D(type, link, parent);
  }
  
  
  public ShadowType makeShadowSetType(SetType type, DataDisplayLink link,
    ShadowType parent)
    throws RemoteException, VisADException 
  {
    return new ShadowScreenLockedSetTypeJ3D(type, link, parent);
  }

  /**
   * Used for testing.
   * Creates a display with a red square and labels at each
   * corner of the square.  The square rotates, and moves as
   * you would expect, but the text is locked to its original
   * position on the screen.
   */
  public static final void main(String [] args)
    throws VisADException, RemoteException
  {
    final DisplayImplJ3D display = new DisplayImplJ3D("display");
    final DisplayRendererJ3D renderer = 
      (DisplayRendererJ3D) display.getDisplayRenderer();
    renderer.setBoxOn(false);
    renderer.setBackgroundColor(0.0f, 0.2f, 1.0f);

    final GraphicsModeControl gmc = 
      (GraphicsModeControl) display.getGraphicsModeControl();
    gmc.setScaleEnable(false);
    gmc.setProjectionPolicy(DisplayImplJ3D.PARALLEL_PROJECTION);

    final RealTupleType domainType = new RealTupleType(RealType.Latitude,
      RealType.Longitude);
    final TextType textType = TextType.getTextType("text");
    final FunctionType functionType = new FunctionType(domainType, textType);

    // The domain samples make up a square. (clockwise order).
    float [][] domainSamples = new float[2][4];
    domainSamples[0][0] = 0f; 
    domainSamples[1][0] = 0f;
    domainSamples[0][1] = 10f;
    domainSamples[1][1] = 0f;
    domainSamples[0][2] = 10f;
    domainSamples[1][2] = 10f;
    domainSamples[0][3] = 0f;
    domainSamples[1][3] = 10f;

    // Create the field that will dislay the labels.
    final Set domainSet1 = new Gridded2DSet(domainType, domainSamples, 4);
    FieldImpl field = new FieldImpl(functionType, domainSet1);
    field.setSample(0, new Text(textType, "Screen Locked 1"));
    field.setSample(1, new Text(textType, "Screen Locked 2"));
    field.setSample(2, new Text(textType, "Screen Locked 3"));
    field.setSample(3, new Text(textType, "Screen Locked 4"));
    final DataReferenceImpl lockedDataRef = new DataReferenceImpl(
      "locked_data_ref");
    lockedDataRef.setData(field);
    
    // Created the filled sqaure.
    final Gridded2DSet domainSet2 = new Gridded2DSet(domainType, 
      domainSamples, 4);
    final Irregular2DSet filledSet = DelaunayCustom.fill(domainSet2);
    final DataReferenceImpl unlockedDataRef = new DataReferenceImpl(
      "unlocked_data_ref");
    unlockedDataRef.setData(filledSet);

    final ScalarMap latMap = new ScalarMap(RealType.Latitude, Display.YAxis);
    final ScalarMap lonMap = new ScalarMap(RealType.Longitude, Display.XAxis);
    final ScalarMap textMap = new ScalarMap(textType, Display.Text);

    display.addMap(latMap);
    display.addMap(lonMap);
    display.addMap(textMap);

    // Center the square in the display.
    latMap.setRange(0, 10);
    lonMap.setRange(0, 10);

    // Center the labels on the corners of the square.
    final TextControl textControl = (TextControl) textMap.getControl();
    textControl.setCenter(true);

    display.addReferences(new ScreenLockedRendererJ3D(), lockedDataRef);

    // Color the square red.
    display.addReference(unlockedDataRef, new ConstantMap [] { 
      new ConstantMap(0.0, Display.Green),
      new ConstantMap(0.0, Display.Blue)});

    // Display the frame.
    final JFrame frame = new JFrame("ScreenLockedRendererJ3D");
    frame.getContentPane().add(display.getComponent());
    frame.setSize(400, 400);
    frame.setVisible(true);
  }

} // class ScreenLockedRendererJ3D
//
// ShadowScreenLockedSetTypeJ3D.java
//

/*
VisAD system for interactive analysis and visualization of numerical
data.  Copyright (C) 1996 - 2002 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/

package visad.bom;

// Java
import java.rmi.RemoteException;

// VisAD
import visad.ControlEvent;
import visad.ControlListener;
import visad.DataDisplayLink;
import visad.GraphicsModeControl;
import visad.MathType;
import visad.ShadowType;
import visad.VisADException;
import visad.VisADGeometryArray;
import visad.java3d.ShadowSetTypeJ3D;
import visad.java3d.MouseBehaviorJ3D;
import visad.java3d.ProjectionControlJ3D;
import visad.java3d.DisplayImplJ3D;

// Java 3D
import javax.media.j3d.Group;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Transform3D;
import javax.vecmath.Vector3d;
import javax.vecmath.Matrix3d;


/**
 * This renderer locks text to the screen.  The display can be
 * panned and zoomed, but the text stays locked to its
 * initial position.
 */
public class ShadowScreenLockedSetTypeJ3D extends ShadowSetTypeJ3D 
{

  private TransformGroup transformGroup = null;
  private ProjectionControlListener projectionControlListener = null;

  public ShadowScreenLockedSetTypeJ3D(MathType type, DataDisplayLink link, 
    ShadowType parent) 
    throws RemoteException, VisADException 
  { 
    super(type, link, parent);

    transformGroup = new TransformGroup(); 
    transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); 
    transformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); 
    transformGroup.setCapability(TransformGroup.ALLOW_CHILDREN_READ);

    // Add our listener to the projection control.
    DisplayImplJ3D display = (DisplayImplJ3D) getDisplay(); 
    ProjectionControlJ3D control = (ProjectionControlJ3D) 
      display.getProjectionControl();
    projectionControlListener = new ProjectionControlListener();
    control.addControlListener(projectionControlListener);

  }


  /** 
   * Adds the text to the scene graph hierarchy.
   * Create a new transform group and place the text under this
   * transform group.  This allows us to set the scale, translation
   * and rotation of the text independently of the rest of the nodes
   * in the scene graph.
   */
  public boolean addTextToGroup(Object group, VisADGeometryArray geomArray, 
    GraphicsModeControl control, float alpha, float [] color)
    throws VisADException
  {
    if (null == transformGroup.getParent()) {
      ((Group) group).addChild(transformGroup);
    }
    return super.addTextToGroup(transformGroup, geomArray, control, alpha, 
      color);
  }

  /** 
   * Adds the group to the scene graph hierarchy.
   * Create a new transform group and place the text under this
   * transform group.  This allows us to set the scale, translation
   * and rotation of the data independently of the rest of the nodes
   * in the scene graph.
   */
  public boolean addToGroup(Object group, VisADGeometryArray geomArray, 
    GraphicsModeControl control, float alpha, float [] color)
    throws VisADException
  {
    if (null == transformGroup.getParent()) {
      ((Group) group).addChild(transformGroup);
    }
    return super.addToGroup(transformGroup, geomArray, control, alpha, 
      color);
  }

  /**
   * When the projection control matrix is changed, this listener
   * will undo the scale, translation and rotation of the text
   * thus locking it to its initial position on the screen.
   */
  private class ProjectionControlListener implements ControlListener
  {

    private boolean first = true;
    private double initialScale;
    private double initialXTrans;
    private double initialYTrans;
    private double initialZTrans;
    private double [] rotation = null;
    private double [] translation = null;
    private double [] scale = null;


    /**
     * Default constructor.
     */
    public ProjectionControlListener()
    {
      rotation = new double[ 3 ]; 
      translation = new double[ 3 ]; 
      scale = new double[ 1 ]; 
    }


    /**
     * Undo all scale, translation and rotation transformations.
     * @param event contains information about this event
    */
    public void controlChanged(ControlEvent event)
    { 
      ProjectionControlJ3D control = 
        (ProjectionControlJ3D) event.getControl();
      double [] projectionControlMatrix = control.getMatrix();
      control = null;
      MouseBehaviorJ3D.unmake_matrix(rotation, scale, translation, 
    	  projectionControlMatrix);

      if (first) { 
        // This is the first time through, so keep a copy
        // of our initial scale and translation.
        // We assume that there is no rotation.
        initialScale = scale[0];
        initialXTrans = translation[0];
        initialYTrans = translation[1];
        initialZTrans = translation[2];
        first = false; 
        projectionControlMatrix = null;
        return;
      }
     
      Transform3D transform = new Transform3D();
      transformGroup.getTransform(transform); 

      // Get the rotation.

      double [][] matrix = new double[4][4];

      int k = 0;
      for ( int i = 0; i < 4; ++i ) {
        for ( int j = 0; j < 4; ++j ) {
          matrix[i][j] = projectionControlMatrix[k++];
        }
      }

      projectionControlMatrix = null;

      Matrix3d rotationMatrix = new Matrix3d();
      rotationMatrix.m00 = matrix[0][0];
      rotationMatrix.m01 = matrix[0][1];
      rotationMatrix.m02 = matrix[0][2];
      rotationMatrix.m10 = matrix[1][0];
      rotationMatrix.m11 = matrix[1][1];
      rotationMatrix.m12 = matrix[1][2];
      rotationMatrix.m20 = matrix[2][0];
      rotationMatrix.m21 = matrix[2][1];
      rotationMatrix.m22 = matrix[2][2];

      matrix = null;

      // Undo the rotaion by finding the inverse of the rotation matrix.
      rotationMatrix.invert();
      rotationMatrix.normalize();

      Transform3D rotationTransform = new Transform3D();
      rotationTransform.set(rotationMatrix);

      // Undo the scaling.
      final double newScale = initialScale / scale[0];

      // Undo the translation by translating back to our initial location.
      Vector3d translationVector = new Vector3d();
      translationVector.x = -1 * (translation[0] - initialXTrans) / scale[0];
      translationVector.y = -1 * (translation[1] - initialYTrans) / scale[0];
      translationVector.z = -1 * (translation[2] - initialZTrans) / scale[0];

      Transform3D scaleTranslationTransform = new Transform3D();
      scaleTranslationTransform.set(newScale, translationVector );

      transform = rotationTransform;
      transform.mul( scaleTranslationTransform ); 
  
      transformGroup.setTransform(transform);
  
      rotationTransform = null;
      scaleTranslationTransform = null;
      transform = null;
    }

  } // class ShadowScreenLockedSetTypeJ3D.ProjectionControlListener

}

// ScreenLockedDemo.java

// Java
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.BorderLayout;
import java.rmi.RemoteException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

// VisAD
import visad.ConstantMap;
import visad.DataReferenceImpl;
import visad.DelaunayCustom;
import visad.Display;
import visad.DisplayImpl;
import visad.FlatField;
import visad.FunctionType;
import visad.GraphicsModeControl;
import visad.Gridded2DDoubleSet;
import visad.Irregular2DSet;
import visad.RealTupleType;
import visad.RealType;
import visad.ScalarMap;
import visad.UnionSet;
import visad.VisADException;
import visad.bom.ScreenLockedRendererJ3D;
import visad.java3d.DefaultRendererJ3D;
import visad.java3d.DisplayImplJ3D;
import visad.java3d.RendererJ3D;


/**
 * This demo illustrates the problems I am experiencing with
 * the screen locked renderer.
 *
 * In the process of creating this demo, I learned that a
 * Set can be screen-locked by using a ShadowScreenLockedSetTypeJ3D.java
 * so I hope you don't mind the addition.  ScreenLockedRenderer.java
 * also had to change to accommodate this new shadow type.
 *
 * The demo illustrates the problem I have been having which
 * involved a radar rain-rate scale which was locked to the
 * screen.  When the program started, the scale was locked
 * to the lower-left of the screen.  When importing some
 * radar data, the scale moved to a new location and stayed
 * locked to this incorrect position.
 *
 * I tried to solve this problem a number of ways, and I
 * found one. (Incidentally, the method does not work for
 * this demo!).
 *
 * But, there was another problem...
 *
 * The x/y/z and scale are re-initialised each
 * time the shadow type was created, but there was a problem
 * when we imported large amounts of radar data and tried
 * panning/zooming while the "please wait" message was
 * displayed.
 *
 * When "please wait" is displayed, and the user pans/zooms the
 * display, the projection's control's controlChanged() events
 * become coarse-grained.  ie. there is a big change in the projection control
 * matrix between successive firings of controlChanged events.
 * So, the scale would move a little and then
 * be re-locked to a position near to where it should be, but off by
 * quite a few pixels.  It would then stay locked to this incorrect position.
 *
 * That's enough about the radar rain-scale...
 * Now to talk about this demo.
 *
 *
 * MathTypes:
 * 	red square: (x,y)
 * 	white cross: (x,y)
 * 	blue triangle: (x,y)
 * 	field: (x,y)->(height)
 * 
 * Locations:
 *	red square: screen-locked
 *	white cross: initially centred on red square
 *	blue triangle: SE of white cross
 *	field: NE of white cross
 *
 *
 * Test 1
 *
 * 	Start the demo
 *		You will see a white cross displayed over a screen-locked
 *		red square
 *	Zoom out a little.
 *	Pan so that the white cross is in the top left corner of the window
 *	Click "Add Triangle"
 *		A blue triangle is displayed near the lower-right corner
 *		of the white cross, as expected.
 *		The red square however has moved over to be centred
 *		under that white cross.  It's also resized so that
 *		fits under the white cross as it did initially.
 * 	Exit
 *
 *
 * Test 2
 *
 * 	Start the demo
 *		You will see a white cross displayed over a screen-locked
 *		red square
 *	Click the "Set Range" button.
 *		This sets the domain ranges (x and y).
 *		This white cross decreases in size.
 *		The red square decreases in size, even though it is
 *		"screen locked"! [alarm bells ring]
 *	Zoom out a little.
 *	Pan so that the white cross is in the top left corner of the window
 *	Click "Add Triangle"
 *		The blue triangle is displayed near the lower-right corner
 *		of the white cross, as expected.
 *		The red square stays screen-locked in the centre of the
 *		screen.
 *	Zoom/pan the display
 *		The red square remains locked to the centre of the screen.
 *	Exit
 *	
 *
 * Test 3
 *
 * 	Start the demo
 *		You will see a white cross displayed over a screen-locked
 *		red square
 *	Optional: click "Set Range"
 *	Zoom out a little.
 *	Pan so that the white cross is in the top left corner of the window
 *	Click "Add Field"
 *		A field is displayed near the top-right of the white cross,
 *		as expected.
 *		The red square however has moved over to be centred
 *		under the white cross again.  It's also resized so that it
 *		fits under the white cross.
 *
 * Conclusions:
 *
 * 	ScalarMap.setRange() is having an effect that I am not
 * 	accounting for.
 *
 *	It seems that setRange() needs to be accounted for, regardless
 *	of which scalar is involved in the setRange.  In "Test 3", visad 
 *	would have done an auto-setRange on the scalar map that maps
 *	the field's range type to Display.RGB.
 *
 * Future problems:
 *
 *	Even if we can learn to correctly handle ScalarMap.setRange(),
 *	there is another problem waiting.
 *
 *	This is the problem that I described earlier regarding the
 *	re-initialisation of the x/y/z translations and scale.  If
 *	a user is pushing the system hard, and "please wait" is
 *	displayed, and the display is zoomed/panned, notification of
 *	changes in the projection control matrix become coarse-grained,
 *	and re-initialisation is done to the wrong location.
 *
 * Other:
 *
 *	As an aside, I would like to mention that the projection
 *	control listeners need to be removed when the shadow is
 *	destroyed.
 *
 * Question:
 *
 * 	Is it possible to not have to re-initialise the x/y/z translations
 * 	and scale?  Perhaps extra logic can be added to ScreenLockedRendererJ3D
 * 	and the ShadowScreenLocked*J3D classes that allow the 
 * 	translations and scale to be initialised only once?
 *
 */
public final class ScreenLockedDemo
{

	private final DisplayImplJ3D display;
	private final ScalarMap xMap;
	private final ScalarMap yMap;


	/**
	 * Constructor.
	 */
	public ScreenLockedDemo() throws VisADException, RemoteException
	{

		display = new DisplayImplJ3D("display");

		final GraphicsModeControl gmc =
			display.getGraphicsModeControl();
		gmc.setScaleEnable(false);
		gmc.setProjectionPolicy(DisplayImplJ3D.PARALLEL_PROJECTION);

		display.getDisplayRenderer().setBoxOn(false);

		final RealType x = RealType.getRealType("x");
		final RealType y = RealType.getRealType("y");

		xMap = new ScalarMap(x, Display.XAxis);
		yMap = new ScalarMap(y, Display.YAxis);

		display.addMap(xMap);
		display.addMap(yMap);

		addScreenLockedSquare(display);
		addCross(display);

		final JFrame frame = new JFrame("Screen Locked Demo");
		final JPanel panel = new JPanel(new BorderLayout());

		panel.add(display.getComponent(), BorderLayout.CENTER);

		final JPanel buttonsPanel = new JPanel();
		final ButtonListener buttonListener = new ButtonListener();

		JButton button = new JButton("Add Triangle");
		button.addActionListener(buttonListener);
		buttonsPanel.add(button);

		button = new JButton("Add Field");
		button.addActionListener(buttonListener);
		buttonsPanel.add(button);

		button = new JButton("Set Range");
		button.addActionListener(buttonListener);
		buttonsPanel.add(button);

		panel.add(buttonsPanel, BorderLayout.SOUTH);

		frame.getContentPane().add(panel);
		frame.setSize(640, 480);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);

	} // ScreenLockedDemo.ScreenLockedDemo()


	/**
	 * Adds a coloured square to the display, using a screen
	 * locked renderer.
	 */
	private static void addScreenLockedSquare(final DisplayImpl display)
		throws VisADException, RemoteException
	{

		final int numSamples = 4;
		final double[][]samples = new double[2][numSamples];
		samples[0][0] = -10;
		samples[1][0] = 10;
		samples[0][1] = 10;
		samples[1][1] = 10;
		samples[0][2] = 10;
		samples[1][2] = -10;
		samples[0][3] = -10;
		samples[1][3] = -10;

		final RealTupleType domainType = new RealTupleType(
			RealType.getRealType("x"), RealType.getRealType("y"));

		final Gridded2DDoubleSet set = new Gridded2DDoubleSet(
			domainType, samples, numSamples);
		final Irregular2DSet filledSet = DelaunayCustom.fill(set);

		final DataReferenceImpl dataRef =
			new DataReferenceImpl("square_data_ref");
		dataRef.setData(filledSet);

		final RendererJ3D renderer = new ScreenLockedRendererJ3D();
		final ConstantMap[] maps = new ConstantMap[] {
			new ConstantMap(1, Display.Red),
			new ConstantMap(0, Display.Green),
			new ConstantMap(0, Display.Blue),
			new ConstantMap(-0.03, Display.ZAxis)};
		display.addReferences(renderer, dataRef, maps);

	} // ScreenLockedDemo.addScreenLockedSquare()


	/**
	 * Adds a triangle to the display, using a default renderer.
	 */
	private static void addTriangle(final DisplayImpl display)
		throws VisADException, RemoteException
	{

		final int numSamples = 3;
		final double[][]samples = new double[2][numSamples];
		samples[0][0] = 12;
		samples[1][0] = -15;
		samples[0][1] = 17;
		samples[1][1] = -10;
		samples[0][2] = 22;
		samples[1][2] = -15;

		final RealTupleType type = new RealTupleType(
			RealType.getRealType("x"), RealType.getRealType("y"));

		final Gridded2DDoubleSet set = new Gridded2DDoubleSet(
			type, samples, numSamples);
		final Irregular2DSet filledSet = DelaunayCustom.fill(set);

		final DataReferenceImpl dataRef =
			new DataReferenceImpl("triangle_data_ref");
		dataRef.setData(filledSet);

		final RendererJ3D renderer = new DefaultRendererJ3D();
		final ConstantMap[] maps = new ConstantMap[] {
			new ConstantMap(0, Display.Red),
			new ConstantMap(0, Display.Green),
			new ConstantMap(1, Display.Blue),
			new ConstantMap(-0.02, Display.ZAxis)};
		display.addReferences(renderer, dataRef, maps);

	} // ScreenLockedDemo.addTriangle()


	/**
	 * Adds a cross to the dispaly, using a  default renderer.
	 */
	private static void addCross(final DisplayImpl display)
		throws VisADException, RemoteException
	{

		final int numSamples = 2;
		final double[][] samples = new double[2][numSamples];

		samples[0][0] = -12;
		samples[1][0] = 0;
		samples[0][1] = 12;
		samples[1][1] = 0;
		final RealTupleType type = new RealTupleType(
			RealType.getRealType("x"), RealType.getRealType("y"));
		Gridded2DDoubleSet horizontalSet = new Gridded2DDoubleSet(
			type, samples, numSamples);

		samples[0][0] = 0;
		samples[1][0] = 12;
		samples[0][1] = 0;
		samples[1][1] = -12;
		Gridded2DDoubleSet verticalSet = new Gridded2DDoubleSet(
			type, samples, numSamples);

		final UnionSet set = new UnionSet(
			new Gridded2DDoubleSet[]{
				horizontalSet, verticalSet});
		final DataReferenceImpl dataRef =
			new DataReferenceImpl("lines_data_ref");
		dataRef.setData(set);
		final ConstantMap[] maps = new ConstantMap[] {
			new ConstantMap(2, Display.LineWidth),
			new ConstantMap(-0.01, Display.ZAxis)};
		display.addReference(dataRef, maps);

	} // ScreenLockedDemo.addCross()


	/**
	 * Adds a field to the display.
	 */
	private static void addField(final DisplayImpl display)
		throws VisADException, RemoteException
	{

		final RealTupleType domainType = new RealTupleType(
			RealType.getRealType("x"), RealType.getRealType("y"));
		final RealType rangeType = RealType.getRealType("height");
		final FunctionType functionType =
			new FunctionType(domainType, rangeType);
		final FlatField field = FlatField.makeField1(
			functionType, 11, 18, 10, 11, 18, 10);
		final DataReferenceImpl dataRef =
			new DataReferenceImpl("field_data_ref");
		dataRef.setData(field);
		display.addMap(new ScalarMap(rangeType, Display.RGB));
		display.addReference(dataRef);

	} // ScreenLockedDemo.addField()


	/**
	 * Listens for button clicks.
	 */
	private class ButtonListener implements ActionListener
	{

		/**
		 * The user has clicked on a button.
		 */
		public void actionPerformed(ActionEvent event)
		{
			

			try {
				final String command = event.getActionCommand();

				if (command.equals("Add Triangle")) {
					addTriangle(display);
				} else if (command.equals("Add Field")) {
					addField(display);
				} else {
					xMap.setRange(-20, 20);
					yMap.setRange(-20, 20);
				}
			} catch (VisADException ex) {
				System.err.println(ex.getMessage());
				ex.printStackTrace();
			} catch (RemoteException ex) {
				System.err.println(ex.getMessage());
				ex.printStackTrace();
			}

		} // ButtonListener.actionPerformed()

	} // class ScreenLockedDemo.ButtonListener


	/**
	 * Used to run the program.
	 *
	 * Please read the class javadoc at the top of this file.
	 *
	 * @param args arguments are ignored.
	 */
	public static final void main(String[] args)
		throws VisADException, RemoteException
	{

		new ScreenLockedDemo();

	} // ScreenLockedDemo.main()

} // class ScreenLockedDemo