com.dfki.av.sudplan.vis.VisualizationPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.dfki.av.sudplan.vis.VisualizationPanel.java

Source

/*
 *  VisualizationPanel.java 
 *
 *  Created by DFKI AV on 15.09.2011.
 *  Copyright (c) 2011-2012 DFKI GmbH, Kaiserslautern. All rights reserved.
 *  Use is subject to license terms.
 */
package com.dfki.av.sudplan.vis;

import com.dfki.av.sudplan.Configuration;
import com.dfki.av.sudplan.camera.*;
import com.dfki.av.sudplan.stereo.SideBySideStereoSetup;
import com.dfki.av.sudplan.vis.core.IVisAlgorithm;
import com.dfki.av.sudplan.vis.core.VisConfiguration;
import com.dfki.av.sudplan.vis.core.VisPointCloud;
import com.dfki.av.sudplan.vis.core.VisWorker;
import com.dfki.av.sudplan.vis.io.IOUtils;
import com.dfki.av.sudplan.vis.spi.VisAlgorithmFactory;
import com.dfki.av.sudplan.vis.wiz.AttributeSelectionPanel;
import com.dfki.av.sudplan.vis.wiz.VisWiz;
import com.dfki.av.sudplan.wms.*;
import gov.nasa.worldwind.Model;
import gov.nasa.worldwind.View;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.WorldWindow;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.avlist.AVList;
import gov.nasa.worldwind.awt.WorldWindowGLCanvas;
import gov.nasa.worldwind.geom.Box;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.layers.*;
import gov.nasa.worldwind.ogc.kml.KMLAbstractFeature;
import gov.nasa.worldwind.ogc.kml.KMLRoot;
import gov.nasa.worldwind.ogc.kml.impl.KMLController;
import gov.nasa.worldwind.ogc.wms.WMSCapabilities;
import gov.nasa.worldwind.ogc.wms.WMSLayerCapabilities;
import gov.nasa.worldwind.ogc.wms.WMSLayerStyle;
import gov.nasa.worldwind.terrain.SectorGeometryList;
import gov.nasa.worldwind.util.StatusBar;
import gov.nasa.worldwind.util.WWUtil;
import gov.nasa.worldwindx.examples.ApplicationTemplate;
import gov.nasa.worldwindx.examples.ClickAndGoSelectListener;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.swing.*;
import org.apache.commons.configuration.XMLConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class containing the {@link WorldWindowGLCanvas} to render the virtual globe.
 *
 * @author Daniel Steffen <daniel.steffen at dfki.de>
 */
public class VisualizationPanel extends JPanel implements VisualizationComponent {

    /*
     * Logger.
     */
    private final Logger log = LoggerFactory.getLogger(VisualizationPanel.class);
    /**
     * The world wind GL canvas.
     */
    private WorldWindowGLCanvas wwd;
    /**
     * The {@link StatusBar} attached to this panel.
     */
    private StatusBar statusBar;
    /**
     * The {@link JProgressBar} added to the {@link #statusBar}.
     */
    private JProgressBar progressBar;
    /**
     * Support for publishing progress support to the universe :)
     */
    private PropertyChangeSupport progressChange;
    /**
     * A {@link LayerPanel} panel to manage the layer settings.
     */
    private LayerPanel layerPanel;

    /**
     * Constructs a visualization panel of the defined
     * <code>Dimension</code>.
     *
     * @param canvasSize size of the
     * <code>WorldWindowGLCanvas</code>.
     */
    public VisualizationPanel(Dimension canvasSize) {
        super(new BorderLayout());

        this.wwd = new WorldWindowGLCanvas();
        this.wwd.setPreferredSize(canvasSize);

        // Create the default model as described in the current worldwind properties.
        Model m = (Model) WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
        this.wwd.setModel(m);

        // Setup a select listener for the worldmap click-and-go feature
        this.wwd.addSelectListener(new ClickAndGoSelectListener(this.wwd, WorldMapLayer.class));

        ViewControlsLayer viewControlsLayer = new ViewControlsLayer();
        this.wwd.getModel().getLayers().add(viewControlsLayer);
        this.wwd.addSelectListener(new ViewControlsSelectListener(this.wwd, viewControlsLayer));
        this.add(this.wwd, BorderLayout.CENTER);

        this.progressBar = new JProgressBar(0, 100);
        this.progressBar.setVisible(false);

        this.statusBar = new StatusBar();
        this.statusBar.setEventSource(wwd);
        this.statusBar.add(progressBar);
        this.add(statusBar, BorderLayout.SOUTH);

        this.progressChange = new PropertyChangeSupport(this);

        this.layerPanel = new LayerPanel(this.wwd);
        this.wwd.getModel().addPropertyChangeListener(layerPanel);

        initCustomElevationModels();
    }

    /**
     * Initialize custom elevation models. Currently, only the Wuppertal
     * elevation model for the area of Lntenbeck.
     */
    private void initCustomElevationModels() {
        XMLConfiguration xmlConfig = Configuration.getXMLConfiguration();
        String key = "sudplan3D.wuppertal.localElevationModel.enabled";
        if (xmlConfig.containsKey(key)) {
            String value = xmlConfig.getString(key);
            if (Boolean.valueOf(value)) {
                log.info("Custom elevation models enabled.");
                Globe globe = this.wwd.getModel().getGlobe();
                String pathKey = "sudplan3D.wuppertal.localElevationModel.path";
                if (xmlConfig.containsKey(pathKey)) {
                    String path = xmlConfig.getString(pathKey);
                    ElevationsLoader loader = new ElevationsLoader(globe, path);
                    loader.execute();
                } else {
                    log.debug("No <path> tag. Custom elevation models disabled.");
                }
            } else {
                log.debug("Custom elevation models disabled.");
            }
        } else {
            log.debug("No <enabled> tag. Custom elevation models disabled.");
        }
    }

    /**
     * Returns the {@link WorldWindowGLCanvas} used by the
     * {@link VisualizationPanel}
     *
     * @return the {@link WorldWindowGLCanvas} to return.
     */
    public WorldWindowGLCanvas getWwd() {
        return this.wwd;
    }

    @Override
    public void addLayer(Object data) {
        IVisAlgorithm algo = VisAlgorithmFactory.newInstance(VisPointCloud.class.getName());
        if (algo != null) {
            addLayer(data, algo, null);
        } else {
            log.error("VisAlgorithm {} not supported.", VisPointCloud.class.getName());
        }
    }

    /**
     * Add a layer visualization to the {@link WorldWindowGLCanvas}. The
     * visualization is created by using the {@code data}, the
     * {@link IVisAlgorithm}, and the {@code attributes}.
     *
     * @param data the data source for the visualization.
     * @param vis the visualization technique.
     * @param attributes the attributes of the data source to be visualized.
     */
    public void addLayer(Object data, IVisAlgorithm vis, Object[] attributes) {
        vis.addPropertyChangeListener(this);
        VisConfiguration config = new VisConfiguration(vis, data, attributes);
        VisWorker producer = new VisWorker(config, wwd);
        producer.execute();
    }

    @Override
    public void removeLayer(Object source) {
        if (source == null) {
            log.warn("Object sourc equals to null.");
            throw new IllegalArgumentException("Parameter 'layer' is null.");
        }

        if (source instanceof Layer) {
            Layer layer = (Layer) source;
            this.wwd.getModel().getLayers().remove(layer);
        } else {
            log.warn("Can't remove object.");
        }
    }

    /**
     * Removes all layers from the World Wind visualization component. <p> Note:
     * This implementation keeps the following layers: <ul> <li>Atmosphere</li>
     * <li>NASA Blue Marble Image</li> <li>Blue Marble (WMS) 2004</li>
     * <li>i-cubed Landsat</li> <li>Place Names</li> <li>Scale bar</li>
     * <li>Compass</li> <li>View Controls</li> </ul>
     */
    @Override
    public void removeAllLayers() {
        LayerList layerList = this.wwd.getModel().getLayers();
        for (Object object : layerList) {
            Layer layer = (Layer) object;
            // TODO <steffen>: Check usage of World Wind constants here.
            if (layer.getName().equalsIgnoreCase("Atmosphere")
                    || layer.getName().equalsIgnoreCase("NASA Blue Marble Image")
                    || layer.getName().equalsIgnoreCase("Blue Marble (WMS) 2004")
                    || layer.getName().equalsIgnoreCase("i-cubed Landsat")
                    || layer.getName().equalsIgnoreCase("Place Names")
                    || layer.getName().equalsIgnoreCase("Scale bar") || layer.getName().equalsIgnoreCase("Compass")
                    || layer.getName().equalsIgnoreCase("View Controls")) {
                log.debug("Not removing layer: {}", layer.getName());
                continue;
            } else {
                log.debug("Removing layer: {}", layer.getName());
                removeLayer(layer);
            }
        }
    }

    @Override
    public Camera getCamera() {
        Camera camera = null;
        View view = this.wwd.getView();
        if (view != null) {
            Position p = view.getEyePosition();
            Angle heading = view.getHeading();
            Angle roll = view.getRoll();
            Angle pitch = view.getPitch();
            Vector3D vector = new Vector3D(roll.getRadians(), pitch.getRadians(), heading.getRadians());
            camera = new SimpleCamera(p, vector);
        } else {
            log.debug("No view available. Could not init camera object.");
        }
        return camera;
    }

    @Override
    public void setCamera(Camera c) {
        if (c == null) {
            String msg = "camera == null";
            log.error(msg);
            throw new IllegalArgumentException(msg);
        }

        View view = this.wwd.getView();
        if (view != null) {

            Angle roll;
            Angle pitch;
            Angle heading;

            Vector3D vector = c.getViewingDirection();

            if (vector != null) {
                roll = Angle.fromRadians(vector.getX());
                pitch = Angle.fromRadians(vector.getY());
                heading = Angle.fromRadians(vector.getZ());
            } else {
                log.warn("No vector defined for camera. Using zero vector.");
                roll = pitch = heading = Angle.ZERO;
            }

            view.setRoll(roll);
            view.setPitch(pitch);
            view.setHeading(heading);

            if (c instanceof AnimatedCamera) {
                Position pos = Position.fromDegrees(c.getLatitude(), c.getLongitude());
                view.goTo(pos, c.getAltitude());
            } else if (c instanceof SimpleCamera) {
                Position pos = Position.fromDegrees(c.getLatitude(), c.getLongitude(), c.getAltitude());
                view.setEyePosition(pos);
                wwd.redraw();
            }
        }
    }

    @Override
    public synchronized void addCameraListener(CameraListener cl) {
        if (cl == null) {
            String msg = "CameraListener == null";
            log.error(msg);
            throw new IllegalArgumentException(msg);
        }
        this.wwd.getView().addPropertyChangeListener("gov.nasa.worldwind.avkey.ViewObject", cl);
    }

    @Override
    public synchronized void removeCameraListener(CameraListener cl) {
        if (cl == null) {
            String msg = "CameraListener == null";
            log.error(msg);
            throw new IllegalArgumentException(msg);
        }
        this.wwd.getView().removePropertyChangeListener("gov.nasa.worldwind.avkey.ViewObject", cl);
    }

    @Override
    public void setBoundingVolume(BoundingVolume bv) {
        if (bv == null) {
            String msg = "BoundingVolume == null";
            log.error(msg);
            throw new IllegalArgumentException(msg);
        }

        Sector sector = bv.getSector();
        Box extent = Sector.computeBoundingBox(wwd.getModel().getGlobe(),
                wwd.getSceneController().getVerticalExaggeration(), sector);
        Angle fov = wwd.getView().getFieldOfView();
        double zoom = extent.getRadius() / fov.cosHalfAngle() / fov.tanHalfAngle();

        LatLon latLon = sector.getCentroid();
        AnimatedCamera ac = new AnimatedCamera(latLon.getLatitude().getDegrees(),
                latLon.getLongitude().getDegrees(), zoom);
        setCamera(ac);
    }

    @Override
    public BoundingVolume getBoundingVolume() {
        SectorGeometryList sectorGeometryList = wwd.getSceneController().getTerrain();
        Sector sector = sectorGeometryList.getSector();
        return new BoundingBox(sector);
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName().equalsIgnoreCase(IVisAlgorithm.PROGRESS_PROPERTY)) {
            Integer i = (Integer) evt.getNewValue();
            if (i.intValue() <= 0 || i.intValue() >= 100) {
                progressBar.setVisible(false);
            } else {
                progressBar.setVisible(true);
            }
            progressBar.setValue(i.intValue());
            progressChange.firePropertyChange(evt);
        }
        if (evt.getPropertyName().equals(EventHolder.WWD_REDRAW)) {
            wwd.redraw();
        }
    }

    @Override
    public void addProgressListener(PropertyChangeListener listener) {
        this.progressChange.addPropertyChangeListener(IVisAlgorithm.PROGRESS_PROPERTY, listener);
    }

    @Override
    public void removeProgressListener(PropertyChangeListener listener) {
        this.progressChange.removePropertyChangeListener(IVisAlgorithm.PROGRESS_PROPERTY, listener);
    }

    /**
     * Run the {@link VisWiz} and add its result to the {@link #wwd}. Starts the
     * wizard with data selection panel.
     */
    public void runVisWiz() {
        runVisWiz(null);
    }

    /**
     * Run the {@link VisWiz} and add its result to the {@link WorldWindowGLCanvas}.
     * Starts the wizard with the {@link AttributeSelectionPanel} in case the
     * {@code data} is a valid data type (see {@link IOUtils#Read(java.lang.Object)
     * }) or not {code null}.
     *
     * @param data the pre-selected data source to visualize.
     * @see IOUtils#Read(java.lang.Object)
     */
    public void runVisWiz(Object data) {
        VisWiz.execute(wwd, this, data);
    }

    /**
     * Adds all available layers of the WMS at {@link URI} to the
     * {@link WorldWindow}. The layers are disabled by default.
     *
     * @param uri the server {@link URI}.
     * @throws IllegalArgumentException if uri == null
     */
    public void addAllWMSLayer(URI uri) {
        if (uri == null) {
            String msg = "URI == null";
            log.error(msg);
            throw new IllegalArgumentException(msg);
        }

        try {
            List<LayerInfo> layerInfos = WMSUtils.getLayerInfos(uri);
            if (layerInfos != null) {
                for (LayerInfo layerInfo : layerInfos) {
                    Object component = LayerInfo.createComponent(layerInfo.caps, layerInfo.params);
                    if (component instanceof Layer) {
                        Layer layer = (Layer) component;
                        LayerList layers = wwd.getModel().getLayers();
                        layer.setEnabled(false);
                        if (!layers.contains(layer)) {
                            ApplicationTemplate.insertBeforePlacenames(wwd, layer);
                        }
                    }
                }
            } else {
                String msg = "No valid URI: " + uri;
                log.error(msg);
                throw new Exception(msg);
            }
        } catch (Exception ex) {
            log.error(ex.getMessage());
            JOptionPane.showMessageDialog(this, ex.toString(), "Error", JOptionPane.ERROR_MESSAGE);
        }
    }

    /**
     * Add the {@code layerName} layer of the WMS server at {@link URI} with the
     * opacity {@code opacity} to the {@link WorldWindowGLCanvas}. The layer is
     * enabled per default.
     *
     * @param uri the {@link URI} to the WMS
     * @param layerName the name of the Layer to add
     * @param opacity the opacity to set
     * @throws IllegalArgumentException if uri == null or layerName == null or
     * {@code opacity < 0} or {@code opacity > 1.0}.
     */
    public void addWMSLayer(URI uri, String layerName, double opacity) {
        if (uri == null) {
            String msg = "uri == null";
            log.error(msg);
            throw new IllegalArgumentException(msg);
        }
        if (layerName == null) {
            String msg = "layerName == null";
            log.error(msg);
            throw new IllegalArgumentException(msg);
        }
        if (layerName.isEmpty()) {
            String msg = "layerName is empty";
            log.error(msg);
            throw new IllegalArgumentException(msg);
        }
        if (opacity < 0.0 || opacity > 1.0) {
            String msg = "opacity out of range";
            log.error(msg);
            throw new IllegalArgumentException(msg);
        }

        LayerInfo layerInfo = WMSUtils.getLayerInfo(uri, layerName);
        Object component = LayerInfo.createComponent(layerInfo.caps, layerInfo.params);
        if (component instanceof Layer) {
            Layer layer = (Layer) component;
            layer.setEnabled(true);
            layer.setOpacity(opacity);
            LayerList layers = wwd.getModel().getLayers();
            if (!layers.contains(layer)) {
                ApplicationTemplate.insertBeforePlacenames(wwd, layer);
            } else {
                log.debug("Layer {} already added.", layer.getName());
            }
        } else {
            log.warn("Component no instance of class Layer.");
        }
    }

    /**
     * Adds a WMS Layer from the given parameters.
     *
     * Note that if the layerName contains '[]' a time series of WMS layer will
     * be added, including a {@link WMSControlLayer}.
     *
     * @param uri {@link URI} from the WMS server
     * @param layerName name of the requested layer or name of the top layer of
     * a time series which must contain a '[]' in the layer name
     * @param elevation the elevation (meters above sea level) for the result
     * layer
     * @param opacity the opacity for the result layer (1.0 : full transparent)
     */
    public void addWMSHeightLayer(URI uri, String layerName, double elevation, double opacity) {
        if (layerName.contains("[]")) {
            log.debug("Layer is instance of a time series.");
            List<LayerInfo> layerInfos = WMSUtils.getLayerInfos(uri);
            List<ElevatedRenderableLayer> layers = new ArrayList<ElevatedRenderableLayer>();

            for (LayerInfo layerInfo : layerInfos) {
                String name = layerInfo.getName();
                if (name.equals(layerName)) {
                    log.debug("Found WMS layer {}.", layerName);

                    WMSCapabilities parentWMSCaps = layerInfo.caps;
                    WMSLayerCapabilities parentWMSLayerCaps = layerInfo.layerCaps;
                    final List<WMSLayerCapabilities> childNamedLayerCaps = parentWMSLayerCaps.getNamedLayers();
                    if (childNamedLayerCaps == null) {
                        log.debug("No child named layers available for parent layer {}.", name);
                        break;
                    }

                    List<LayerInfo> childLayerInfos = new ArrayList<LayerInfo>();
                    for (WMSLayerCapabilities lc : childNamedLayerCaps) {
                        if (lc.isLeaf()) {
                            Set<WMSLayerStyle> styles = lc.getStyles();
                            if (styles == null || styles.isEmpty()) {
                                LayerInfo tmp = new LayerInfo(parentWMSCaps, lc, null);
                                childLayerInfos.add(tmp);
                            } else {
                                for (WMSLayerStyle style : styles) {
                                    LayerInfo tmp = new LayerInfo(parentWMSCaps, lc, style);
                                    childLayerInfos.add(tmp);
                                }
                            }
                        }
                    }

                    for (Iterator<LayerInfo> it = childLayerInfos.iterator(); it.hasNext();) {
                        LayerInfo info = it.next();
                        ElevatedRenderableLayer layer = addWMSHeightLayer(info, elevation, opacity);
                        if (layer != null) {
                            layer.setSlave(true);
                            layer.setOpacity(0.0d);
                            log.debug("Adding layer {}", info.getName());
                            layers.add(layer);
                            layer.getSupportLayer().addPropertyChangeListener(this);
                        }
                    }
                }
            }

            if (!layers.isEmpty()) {
                WMSControlLayer cl = new WMSControlLayer(layers);
                cl.setName(layerName);
                WMSControlListener clistener = new WMSControlListener(cl, layers);
                wwd.addSelectListener(clistener);
                clistener.addPropertyChangeListener(this);
                ApplicationTemplate.insertBeforePlacenames(wwd, cl);
            } else {
                log.debug("No layer found for the time series");
            }
        } else {
            LayerInfo layerInfo = WMSUtils.getLayerInfo(uri, layerName);
            addWMSHeightLayer(layerInfo, elevation, opacity);
        }
    }

    /**
     * Adds a WMS Layer from the given parameters.
     *
     * @param layerInfo the {@link LayerInfo}
     * @param elevation the elevation (meters above sea level) for the result
     * layer
     * @param opacity the opacity for the result layer (1.0 : full transparent)
     */
    private ElevatedRenderableLayer addWMSHeightLayer(LayerInfo layerInfo, double elevation, double opacity) {
        if (layerInfo != null) {
            WMSCapabilities caps = layerInfo.caps;
            WMSLayerCapabilities lcaps = layerInfo.layerCaps;
            AVList params = layerInfo.params;
            ElevatedRenderableLayer sul = new ElevatedRenderableLayer(caps, params, elevation, opacity);
            sul.setName(params.getStringValue(AVKey.DISPLAY_NAME) + "_" + elevation);
            sul.addPropertyChangeListener(this);
            ApplicationTemplate.insertBeforePlacenames(wwd, sul);
            ApplicationTemplate.insertBeforePlacenames(wwd, sul.getSupportLayer());
            return sul;
        }
        return null;
    }

    /**
     * Adds a KML file to the world wind model.
     *
     * @param file the {@link File}
     * @throws IllegalArgumentException if file == null
     * @throws Exception
     */
    public void addKMLLayer(File file) throws Exception {
        if (file == null) {
            String msg = "file == null";
            log.error(msg);
            throw new IllegalArgumentException(msg);
        }

        final File kmlFile = file;
        SwingWorker workerThread = new SwingWorker() {

            @Override
            protected Object doInBackground() throws Exception {
                KMLRoot kmlRoot = KMLRoot.createAndParse(kmlFile);
                KMLAbstractFeature rootFeature = kmlRoot.getFeature();
                String layerName = "KML Layer";
                if (rootFeature != null && !WWUtil.isEmpty(rootFeature.getName())) {
                    layerName = rootFeature.getName();
                } else if (kmlFile instanceof File) {
                    layerName = kmlFile.getName();
                }
                kmlRoot.setField(AVKey.DISPLAY_NAME, layerName);

                KMLController kmlController = new KMLController(kmlRoot);
                // Adds a new layer containing the KMLRoot to the end of the WorldWindow's layer list. This
                // retrieves the layer name from the KMLRoot's DISPLAY_NAME field.
                RenderableLayer layer = new RenderableLayer();
                layer.setName((String) kmlRoot.getField(AVKey.DISPLAY_NAME));
                layer.addRenderable(kmlController);
                ApplicationTemplate.insertBeforePlacenames(wwd, layer);
                return null;
            }
        };
        workerThread.execute();
    }

    /**
     * Adds a GeoTiff file to the world wind model.
     *
     * @param file the {@link File} to add.
     * @throws IllegalArgumentException if file == null
     * @throws IOException
     */
    public void addGeoTiffLayer(File file) throws IOException {
        if (file == null) {
            String msg = "file == null";
            log.error(msg);
            throw new IllegalArgumentException(msg);
        }

        final File geotiffFile = file;
        SwingWorker workerThread = new SwingWorker() {

            @Override
            protected Object doInBackground() throws Exception {
                SurfaceImageLayer layer = new SurfaceImageLayer();
                layer.setPickEnabled(false);
                layer.setValue(AVKey.DISPLAY_NAME, geotiffFile.getName());
                layer.addImage(geotiffFile.getAbsolutePath());
                ApplicationTemplate.insertBeforePlacenames(wwd, layer);
                return null;
            }
        };
        workerThread.execute();
    }

    /**
     * Returns the {@link LayerPanel} that manages all currently available layer
     * elements. The structure for the UI is currently a tree.
     *
     * @return the {@link LayerPanel} to return.
     */
    public LayerPanel getLayerPanel() {
        return this.layerPanel;
    }

    /**
     * Switches to stereoscopic side-by-side mode in full screen.
     *
     * @param parent the {@link JFrame} as parent frame.
     * @throws IllegalArgumentException if parent == null
     */
    public void startStereo(JFrame parent) {
        if (parent == null) {
            String msg = "parent == null";
            log.error(msg);
            throw new IllegalArgumentException(msg);

        }
        SideBySideStereoSetup stereoSetup = new SideBySideStereoSetup(parent, wwd);
        stereoSetup.start();
    }
}