/* Copyright (C) 2001, 2006 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. */ package gov.nasa.worldwind; import gov.nasa.worldwind.avlist.AVKey; import gov.nasa.worldwind.cache.TextureCache; import gov.nasa.worldwind.geom.Position; import gov.nasa.worldwind.globes.*; import gov.nasa.worldwind.layers.Layer; import gov.nasa.worldwind.pick.*; import gov.nasa.worldwind.render.*; import gov.nasa.worldwind.util.*; import javax.media.opengl.*; import java.awt.*; import java.util.*; import java.util.List; import java.util.logging.Level; /** * @author tag * @version $Id: AbstractSceneController.java 5121 2008-04-22 17:54:54Z tgaskins $ */ public abstract class AbstractSceneController extends WWObjectImpl implements SceneController { private Model model; private View view; private double verticalExaggeration = 1d; private DrawContext dc = new DrawContextImpl(); private PickedObjectList lastPickedObjects;// These are for tracking performance private long frame = 0; private long timebase = System.currentTimeMillis(); private double framesPerSecond; private double frameTime; private double pickTime; private Point pickPoint = null; private TextureCache textureCache; private TextRendererCache textRendererCache = new TextRendererCache(); private Set<String> perFrameStatisticsKeys = new HashSet<String>(); private Collection<PerformanceStatistic> perFrameStatistics = new ArrayList<PerformanceStatistic>(); public AbstractSceneController() { this.setVerticalExaggeration(Configuration.getDoubleValue(AVKey.VERTICAL_EXAGGERATION, 1d)); } public void reinitialize() { if (this.textRendererCache != null) this.textRendererCache.dispose(); this.textRendererCache = new TextRendererCache(); } /** * Releases resources associated with this scene controller. */ public void dispose() { if (this.lastPickedObjects != null) this.lastPickedObjects.clear(); this.lastPickedObjects = null; if (this.dc != null) this.dc.dispose(); if (this.textRendererCache != null) this.textRendererCache.dispose(); } public TextureCache getTextureCache() { return textureCache; } public void setTextureCache(TextureCache textureCache) { this.textureCache = textureCache; } public TextRendererCache getTextRendererCache() { return textRendererCache; } public Model getModel() { return this.model; } public View getView() { return this.view; } public void setModel(Model model) { if (this.model != null) this.model.removePropertyChangeListener(this); if (model != null) model.addPropertyChangeListener(this); Model oldModel = this.model; this.model = model; this.firePropertyChange(AVKey.MODEL, oldModel, model); } public void setView(View view) { if (this.view != null) this.view.removePropertyChangeListener(this); if (view != null) view.addPropertyChangeListener(this); View oldView = this.view; this.view = view; this.firePropertyChange(AVKey.VIEW, oldView, view); } public void setVerticalExaggeration(double verticalExaggeration) { Double oldVE = this.verticalExaggeration; this.verticalExaggeration = verticalExaggeration; this.firePropertyChange(AVKey.VERTICAL_EXAGGERATION, oldVE, verticalExaggeration); } public double getVerticalExaggeration() { return this.verticalExaggeration; } public void setPickPoint(java.awt.Point pickPoint) { this.pickPoint = pickPoint; } public Point getPickPoint() { return this.pickPoint; } public PickedObjectList getPickedObjectList() { return this.lastPickedObjects; } protected void setPickedObjectList(PickedObjectList pol) { this.lastPickedObjects = pol; } public SectorGeometryList getTerrain() { return this.dc.getSurfaceGeometry(); } public DrawContext getDrawContext() { return this.dc; } public double getFramesPerSecond() { return this.framesPerSecond; } public double getFrameTime() { return this.frameTime; } public void setPerFrameStatisticsKeys(Set<String> keys) { this.perFrameStatisticsKeys.clear(); if (keys == null) return; for (String key : keys) { if (key != null) this.perFrameStatisticsKeys.add(key); } } public Collection<PerformanceStatistic> getPerFrameStatistics() { return perFrameStatistics; } public void repaint() { this.frameTime = System.currentTimeMillis(); this.perFrameStatistics.clear(); this.initializeDrawContext(this.dc); this.doRepaint(this.dc); ++this.frame; long time = System.currentTimeMillis(); this.frameTime = System.currentTimeMillis() - this.frameTime; if (time - this.timebase > 2000) // recalculate every three seconds { this.framesPerSecond = frame * 1000d / (time - timebase); this.timebase = time; this.frame = 0; } this.dc.setPerFrameStatistic(PerformanceStatistic.FRAME_TIME, "Frame Time (ms)", (int) this.frameTime); this.dc.setPerFrameStatistic(PerformanceStatistic.FRAME_RATE, "Frame Rate (fps)", (int) this.framesPerSecond); this.dc.setPerFrameStatistic(PerformanceStatistic.PICK_TIME, "Pick Time (ms)", (int) this.pickTime); Set<String> perfKeys = dc.getPerFrameStatisticsKeys(); if (perfKeys == null) return; if (perfKeys.contains(PerformanceStatistic.MEMORY_CACHE) || perfKeys.contains(PerformanceStatistic.ALL)) { this.dc.setPerFrameStatistics(WorldWind.getMemoryCacheSet().getPerformanceStatistics()); } if (perfKeys.contains(PerformanceStatistic.TEXTURE_CACHE) || perfKeys.contains(PerformanceStatistic.ALL)) { if (dc.getTextureCache() != null) this.dc.setPerFrameStatistic(PerformanceStatistic.TEXTURE_CACHE, "Texture Cache size (Kb)", this.dc.getTextureCache().getUsedCapacity() / 1000); } } abstract protected void doRepaint(DrawContext dc); private void initializeDrawContext(DrawContext dc) { dc.initialize(GLContext.getCurrent()); dc.setPerFrameStatisticsKeys(this.perFrameStatisticsKeys, this.perFrameStatistics); dc.setTextureCache(this.textureCache); dc.setTextRendererCache(this.textRendererCache); dc.setModel(this.model); dc.setView(this.view); dc.setVerticalExaggeration(this.verticalExaggeration); dc.setPickPoint(this.pickPoint); dc.setViewportCenterScreenPoint(this.getViewportCenter(dc)); } private Point getViewportCenter(DrawContext dc) { View view = dc.getView(); if (view == null) return null; Rectangle viewport = view.getViewport(); if (viewport == null) return null; return new Point((int) (viewport.getCenterX() + 0.5), (int) (viewport.getCenterY() + 0.5)); } protected void initializeFrame(DrawContext dc) { if (dc.getGLContext() == null) { String message = Logging.getMessage("BasicSceneController.GLContextNullStartRedisplay"); Logging.logger().severe(message); throw new IllegalStateException(message); } javax.media.opengl.GL gl = dc.getGL(); gl.glPushAttrib(GL.GL_VIEWPORT_BIT | GL.GL_ENABLE_BIT | GL.GL_TRANSFORM_BIT); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPushMatrix(); gl.glLoadIdentity(); gl.glMatrixMode(GL.GL_PROJECTION); gl.glPushMatrix(); gl.glLoadIdentity(); gl.glEnable(GL.GL_DEPTH_TEST); } protected void clearFrame(DrawContext dc) { Color cc = dc.getClearColor(); dc.getGL().glClearColor(cc.getRed(), cc.getGreen(), cc.getBlue(), cc.getAlpha()); dc.getGL().glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); } protected void finalizeFrame(DrawContext dc) { GL gl = dc.getGL(); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPopMatrix(); gl.glMatrixMode(GL.GL_PROJECTION); gl.glPopMatrix(); gl.glPopAttrib(); // checkGLErrors(dc); } protected void applyView(DrawContext dc) { if (dc.getView() != null) dc.getView().apply(dc); } protected void createTerrain(DrawContext dc) { if (dc.getSurfaceGeometry() == null) { if (dc.getModel() != null && dc.getModel().getGlobe() != null) { SectorGeometryList sgl = dc.getModel().getGlobe().tessellate(dc); dc.setSurfaceGeometry(sgl); dc.setVisibleSector(sgl.getSector()); } if (dc.getSurfaceGeometry() == null) { Logging.logger().warning("generic.NoSurfaceGeometry"); dc.setPerFrameStatistic(PerformanceStatistic.TERRAIN_TILE_COUNT, "Terrain Tiles", 0); // keep going because some layers, etc. may have meaning w/o surface geometry } dc.setPerFrameStatistic(PerformanceStatistic.TERRAIN_TILE_COUNT, "Terrain Tiles", dc.getSurfaceGeometry().size()); } } private ArrayList<Point> pickPoints = new ArrayList<Point>(); protected void pickTerrain(DrawContext dc) { if (dc.isPickingMode() && dc.getVisibleSector() != null && dc.getSurfaceGeometry() != null && dc.getSurfaceGeometry().size() > 0) { this.pickPoints.clear(); if (dc.getPickPoint() != null) this.pickPoints.add(dc.getPickPoint()); // Clear viewportCenterPosition. dc.setViewportCenterPosition(null); Point vpc = dc.getViewportCenterScreenPoint(); if (vpc != null) this.pickPoints.add(vpc); if (this.pickPoints.size() == 0) return; List<PickedObject> pickedObjects = dc.getSurfaceGeometry().pick(dc, this.pickPoints); if (pickedObjects == null || pickedObjects.size() == 0) return; for (PickedObject po : pickedObjects) { if (po == null) continue; if (po.getPickPoint().equals(dc.getPickPoint())) dc.addPickedObject(po); else if (po.getPickPoint().equals(vpc)) dc.setViewportCenterPosition((Position) po.getObject()); } } } protected void pickLayers(DrawContext dc) { if (dc.getLayers() != null) { for (Layer layer : dc.getLayers()) { try { if (layer != null && layer.isPickEnabled()) layer.pick(dc, dc.getPickPoint()); } catch (Exception e) { String message = Logging.getMessage("SceneController.ExceptionWhilePickingInLayer", (layer != null ? layer.getClass().getName() : Logging.getMessage("term.unknown"))); Logging.logger().log(Level.SEVERE, message, e); // Don't abort; continue on to the next layer. } } } } protected void resolveTopPick(DrawContext dc) { // Make a last reading to find out which is a top (resultant) color PickedObjectList pickedObjectsList = dc.getPickedObjects(); if (pickedObjectsList != null && (pickedObjectsList.size() == 1)) { pickedObjectsList.get(0).setOnTop(); } else if (pickedObjectsList != null && (pickedObjectsList.size() > 1)) { int[] viewport = new int[4]; java.nio.ByteBuffer pixel = com.sun.opengl.util.BufferUtil.newByteBuffer(3); GL gl = dc.getGL(); gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0); gl.glReadPixels(dc.getPickPoint().x, viewport[3] - dc.getPickPoint().y, 1, 1, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, pixel); Color topColor = new Color(pixel.get(0) & 0xff, pixel.get(1) & 0xff, pixel.get(2) & 0xff, 0); int colorCode = topColor.getRGB(); if (0 != colorCode) { // let's find the picked object in the list and set "OnTop" flag for (PickedObject po : pickedObjectsList) { if (null != po && po.getColorCode() == colorCode) { po.setOnTop(); break; } } } } // end of top pixel reading } protected void pick(DrawContext dc) { this.pickTime = System.currentTimeMillis(); this.lastPickedObjects = null; try { dc.enablePickingMode(); this.pickTerrain(dc); if (dc.getPickPoint() == null) return; this.pickLayers(dc); // Pick against the deferred/ordered renderables while (dc.getOrderedRenderables().peek() != null) { dc.getOrderedRenderables().poll().pick(dc, dc.getPickPoint()); } this.resolveTopPick(dc); this.lastPickedObjects = new PickedObjectList(dc.getPickedObjects()); } catch (Throwable e) { Logging.logger().log(Level.SEVERE, Logging.getMessage("BasicSceneController.ExceptionDuringPick"), e); } finally { dc.disablePickingMode(); this.pickTime = System.currentTimeMillis() - this.pickTime; } } protected void draw(DrawContext dc) { try { if (dc.getLayers() != null) { for (Layer layer : dc.getLayers()) { try { if (layer != null) layer.render(dc); } catch (Exception e) { String message = Logging.getMessage("SceneController.ExceptionWhileRenderingLayer", (layer != null ? layer.getClass().getName() : Logging.getMessage("term.unknown"))); Logging.logger().log(Level.SEVERE, message, e); // Don't abort; continue on to the next layer. } } } while (dc.getOrderedRenderables().peek() != null) { dc.getOrderedRenderables().poll().render(dc); } // Diagnostic displays. if (dc.getSurfaceGeometry() != null && dc.getModel() != null && (dc.getModel().isShowWireframeExterior() || dc.getModel().isShowWireframeInterior() || dc.getModel().isShowTessellationBoundingVolumes())) { Model model = dc.getModel(); float[] previousColor = new float[4]; dc.getGL().glGetFloatv(GL.GL_CURRENT_COLOR, previousColor, 0); for (SectorGeometry sg : dc.getSurfaceGeometry()) { if (model.isShowWireframeInterior() || model.isShowWireframeExterior()) sg.renderWireframe(dc, model.isShowWireframeInterior(), model.isShowWireframeExterior()); if (model.isShowTessellationBoundingVolumes()) { dc.getGL().glColor3d(1, 0, 0); sg.renderBoundingVolume(dc); } } dc.getGL().glColor4fv(previousColor, 0); } } catch (Throwable e) { Logging.logger().log(Level.SEVERE, Logging.getMessage("BasicSceneController.ExceptionDuringRendering"), e); } } /** * Called to check for openGL errors. This method includes a "round-trip" between the application and renderer, * which is slow. Therefore, this method is excluded from the "normal" render pass. It is here as a matter of * convenience to developers, and is not part of the API. * * @param dc the relevant <code>DrawContext</code> */ @SuppressWarnings({"UNUSED_SYMBOL", "UnusedDeclaration"}) protected void checkGLErrors(DrawContext dc) { GL gl = dc.getGL(); int err = gl.glGetError(); if (err != GL.GL_NO_ERROR) { String msg = dc.getGLU().gluErrorString(err); msg += err; Logging.logger().severe(msg); } } }