/*******************************************************************************
* Copyright 2012 Geoscience Australia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package au.gov.ga.earthsci.worldwind.common.render;
import gov.nasa.worldwind.BasicSceneController;
import gov.nasa.worldwind.SceneController;
import gov.nasa.worldwind.geom.Sector;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.render.SurfaceObjectTileBuilder;
import gov.nasa.worldwind.terrain.SectorGeometryList;
import gov.nasa.worldwind.terrain.Tessellator;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import au.gov.ga.earthsci.worldwind.common.effects.Effect;
import au.gov.ga.earthsci.worldwind.common.exaggeration.VerticalExaggerationListener;
import au.gov.ga.earthsci.worldwind.common.exaggeration.VerticalExaggerationService;
import au.gov.ga.earthsci.worldwind.common.util.SectorClipPlanes;
import au.gov.ga.earthsci.worldwind.common.view.delegate.IDelegateView;
/**
* {@link SceneController} that uses a separate {@link Tessellator} to generate
* a separate set of flat geometry, used by layers that are rendered onto a flat
* surface.
* <p/>
* Also provides the ability to add {@link PaintTask}'s to call before or after
* a repaint occurs.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*/
public class ExtendedSceneController extends BasicSceneController implements DrawableSceneController,
VerticalExaggerationListener
{
private FlatRectangularTessellator flatTessellator = new FlatRectangularTessellator();
protected final SectorClipPlanes sectorClipping = new SectorClipPlanes();
protected final Queue<PaintTask> prePaintTasks = new LinkedList<PaintTask>();
protected final Lock prePaintTasksLock = new ReentrantLock(true);
protected final Queue<PaintTask> postPaintTasks = new LinkedList<PaintTask>();
protected final Lock postPaintTasksLock = new ReentrantLock(true);
protected final List<Effect> effects = new ArrayList<Effect>();
public ExtendedSceneController()
{
dc = wrapDrawContext(dc);
VerticalExaggerationService.INSTANCE.addListener(this);
setVerticalExaggeration(VerticalExaggerationService.INSTANCE.get());
}
protected ExtendedDrawContext wrapDrawContext(DrawContext dc)
{
return new ExtendedDrawContext(dc);
}
@Override
protected SurfaceObjectTileBuilder createSurfaceObjectTileBuilder()
{
return new OffscreenSurfaceObjectRenderer();
}
public void clipSector(Sector sector)
{
sectorClipping.clipSector(sector);
}
public void clearClipping()
{
sectorClipping.clear();
}
@Override
protected void createTerrain(DrawContext dc)
{
super.createTerrain(dc);
if (dc instanceof ExtendedDrawContext)
{
ExtendedDrawContext edc = (ExtendedDrawContext) dc;
if (edc.getFlatSurfaceGeometry() == null)
{
if (dc.getModel() != null && dc.getModel().getGlobe() != null)
{
SectorGeometryList sgl = flatTessellator.tessellate(dc);
edc.setFlatSurfaceGeometry(sgl);
}
}
}
}
@Override
public void doRepaint(DrawContext dc)
{
doPrePaintTasks(dc);
this.initializeFrame(dc);
try
{
this.applyView(dc);
this.createPickFrustum(dc);
this.createTerrain(dc);
this.preRender(dc);
this.clearFrame(dc);
if (view instanceof IDelegateView)
{
((IDelegateView) view).pick(dc, this);
}
else
{
this.pick(dc);
}
this.clearFrame(dc);
if (view instanceof IDelegateView)
{
((IDelegateView) view).draw(dc, this);
}
else
{
this.draw(dc);
}
}
finally
{
this.finalizeFrame(dc);
}
doPostPaintTasks(dc);
}
/**
* @return Draw dimensions used when drawing with this scene controller
*/
protected Dimension getDrawDimensions()
{
return new Dimension(dc.getDrawableWidth(), dc.getDrawableHeight());
}
/**
* @return {@link Effect}s used when drawing with this scene controller
*/
public List<Effect> getEffects()
{
return effects;
}
/**
* Same as {@link #getEffects()}, with a generic return type, for subclasses
* to override.
*
* @return Same as {@link #getEffects()}
*/
protected List<? extends Effect> getDrawEffects()
{
return getEffects();
}
@Override
public void pick(DrawContext dc)
{
super.pick(dc);
}
@Override
public void draw(DrawContext dc)
{
//The draw call is the lowest level rendering call for the SceneController,
//so we still have depth information at this level, which is needed for DoF.
Dimension dimensions = getDrawDimensions();
//retrieve the list of effects, put them in a new list
List<Effect> effects = new ArrayList<Effect>(getDrawEffects());
//remove any disabled effects
for (int i = effects.size() - 1; i >= 0; i--)
{
Effect effect = effects.get(i);
if (!effect.isEnabled())
{
effect.releaseResources(dc);
effects.remove(i);
}
}
//if there's no enabled effects, draw normally
if (effects.isEmpty())
{
clippedDraw(dc);
return;
}
Effect firstEffect = effects.get(0);
Effect lastEffect = effects.get(effects.size() - 1);
try
{
firstEffect.bindFrameBuffer(dc, dimensions);
this.clearFrame(dc);
//draw the actual scene onto the first effect's frame buffer:
clippedDraw(dc);
}
finally
{
firstEffect.unbindFrameBuffer(dc, dimensions);
}
for (int i = 1; i < effects.size(); i++)
{
try
{
effects.get(i).bindFrameBuffer(dc, dimensions);
this.clearFrame(dc);
//draw the previous effect's frame buffer onto the current frame buffer:
effects.get(i - 1).drawFrameBufferWithEffect(dc, dimensions);
}
finally
{
effects.get(i).unbindFrameBuffer(dc, dimensions);
}
}
lastEffect.drawFrameBufferWithEffect(dc, dimensions); //draw the final effect's frame buffer onto the final buffer
}
protected void clippedDraw(DrawContext dc)
{
try
{
sectorClipping.enableClipping(dc);
doDraw(dc);
}
finally
{
sectorClipping.disableClipping(dc);
}
}
protected void doDraw(DrawContext dc)
{
beforeDrawLayers(dc);
super.draw(dc);
}
@Override
protected void preRender(DrawContext dc)
{
try
{
dc.setPreRenderMode(true);
beforePreRenderLayers(dc);
super.preRender(dc);
}
finally
{
dc.setPreRenderMode(false);
}
}
@Override
protected void preRenderOrderedSurfaceRenderables(DrawContext dc)
{
//preRenderOrderedSurfaceRenderables is called immediately after prerendering the layer list, so we
//can inject our overridable function here
afterPreRenderLayers(dc);
super.preRenderOrderedSurfaceRenderables(dc);
}
@Override
protected void drawOrderedSurfaceRenderables(DrawContext dc)
{
//drawOrderedSurfaceRenderables is called immediately after drawing the layer list, so we can inject
//our overridable function here
afterDrawLayers(dc);
super.drawOrderedSurfaceRenderables(dc);
//If we disable sector clipping here, the ordered renderables are not clipped.
//This means the HUD layers are not clipped, but this has the side-effect that
//wireframe elevation isn't clipped, and nor is the graticule.
sectorClipping.disableClipping(dc);
}
@Override
public void clearFrame(DrawContext dc)
{
super.clearFrame(dc);
}
@Override
public void applyView(DrawContext dc)
{
super.applyView(dc);
}
@Override
protected void pickTerrain(DrawContext dc)
{
try
{
super.pickTerrain(dc);
}
catch (ConcurrentModificationException e)
{
//ignore CME, seems to be a bug in the SectorGeometryList
}
}
@Override
protected void pickLayers(DrawContext dc)
{
beforePickLayers(dc);
super.pickLayers(dc);
afterPickLayers(dc);
}
/**
* Called immediately before the layer list is prerendered. Subclasses can
* override to add custom functionality.
*
* @param dc
*/
protected void beforePreRenderLayers(DrawContext dc)
{
}
/**
* Called immediately after the layer list is prerendered. Subclasses can
* override to add custom functionality.
*
* @param dc
*/
protected void afterPreRenderLayers(DrawContext dc)
{
}
/**
* Called immediately before the layer list is drawn. Subclasses can
* override to add custom functionality.
*
* @param dc
*/
protected void beforeDrawLayers(DrawContext dc)
{
}
/**
* Called immediately after the layer list is drawn. Subclasses can override
* to add custom functionality.
*
* @param dc
*/
protected void afterDrawLayers(DrawContext dc)
{
}
/**
* Called immediately before the layer list is picked. Subclasses can
* override to add custom functionality.
*
* @param dc
*/
protected void beforePickLayers(DrawContext dc)
{
}
/**
* Called immediately after the layer list is picked. Subclasses can
* override to add custom functionality.
*
* @param dc
*/
protected void afterPickLayers(DrawContext dc)
{
}
@Override
public void verticalExaggerationChanged(double oldValue, double newValue)
{
if (getVerticalExaggeration() != newValue)
{
//keep this in sync with the vertical exaggeration service
setVerticalExaggeration(newValue);
}
}
@Override
public void setVerticalExaggeration(double verticalExaggeration)
{
super.setVerticalExaggeration(verticalExaggeration);
//keep the vertical exaggeration service in sync with this
VerticalExaggerationService.INSTANCE.set(verticalExaggeration);
}
/**
* Add a task to be executed on the render thread prior to painting
*/
public void addPrePaintTask(PaintTask r)
{
prePaintTasksLock.lock();
prePaintTasks.add(r);
prePaintTasksLock.unlock();
}
/**
* Add a task to be executed on the render thread immediately after
*/
public void addPostPaintTask(PaintTask r)
{
postPaintTasksLock.lock();
postPaintTasks.add(r);
postPaintTasksLock.unlock();
}
protected void doPrePaintTasks(DrawContext dc)
{
prePaintTasksLock.lock();
try
{
while (!prePaintTasks.isEmpty())
{
prePaintTasks.remove().run(dc);
}
}
finally
{
prePaintTasksLock.unlock();
}
}
protected void doPostPaintTasks(DrawContext dc)
{
postPaintTasksLock.lock();
try
{
while (!postPaintTasks.isEmpty())
{
postPaintTasks.remove().run(dc);
}
}
finally
{
postPaintTasksLock.unlock();
}
}
}