I'm trying to implement my own clutter filter in NASA Worldwind for Java and its causing a weird problem -- the clutter filter isn't doing much yet, but I will use it to move things around when I get passed the "flickering" issue. Whenever the mouse is moved the GlobeAnnotation renderables are flickering. When I have the clutter filter set to null, the flickering does not seem to occur.
Here is a GIF that shows what I mean: https://media.giphy.com/media/xT9IgFiZwYZ3VJHQU8/giphy.gif
I've cloned the NASA worldwind code from here: https://github.com/NASAWorldWind/WorldWindJava. I've made a couple of changes to make things work for my eventual filter. One note is that I want the GlobeAnnotations to appear as Always On Top of everything else.
How can I make the GlobeAnnotations not fight with each other and flicker, but still appear on top of everything else -- while having the Clutter Filter turned on?
Note that the following code is just an example I put together to show the issue that I'm seeing in my "real" application. I want the GlobeAnnotations to always be on top of everything else -- but not flickering and fighting with each other.
Here is my test driver:
package gov.nasa.worldwindx.examples;
import java.awt.Color;
import gov.nasa.worldwind.geom.LatLon;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.layers.AnnotationLayer;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.render.GlobeAnnotation;
import gov.nasa.worldwind.render.Material;
import gov.nasa.worldwind.render.airspaces.CappedCylinder;
public class FlashyAnnotations extends ApplicationTemplate {
@SuppressWarnings("unchecked")
private static class AppFrame extends ApplicationTemplate.AppFrame {
private AnnotationLayer layer;
public AppFrame() {
this.getWwd().getSceneController().setClutterFilter(new SimpleClutterFilter());
CappedCylinder cappedCyl = new CappedCylinder(LatLon.fromDegrees(27, -100), 3000000);
cappedCyl.getAttributes().setDrawInterior(true);
cappedCyl.getAttributes().setInteriorMaterial(Material.GREEN);
cappedCyl.getAttributes().setInteriorOpacity(.75f);
cappedCyl.setAltitudes(10, 100000);
RenderableLayer renderLayer = new RenderableLayer();
renderLayer.addRenderable(cappedCyl);
insertBeforeCompass(this.getWwd(), renderLayer);
// Create example annotations
this.setupAnnotations();
}
private void setupAnnotations() {
// Create an AnnotationLayer with lots of annotations
this.layer = new AnnotationLayer();
GlobeAnnotation ga = new GlobeAnnotation("Annotation", Position.fromDegrees(20, -100.9, 1000));
ga.getAttributes().setTextColor(Color.white);
ga.getAttributes().setBackgroundColor(Color.BLACK);
ga.getAttributes().setOpacity(.75f);
ga.setAlwaysOnTop(true);
layer.addAnnotation(ga);
ga = new GlobeAnnotation("Annotation", Position.fromDegrees(25, -100.9, 1000));
ga.getAttributes().setTextColor(Color.white);
ga.getAttributes().setBackgroundColor(Color.BLACK);
ga.getAttributes().setOpacity(.75f);
ga.setAlwaysOnTop(true);
layer.addAnnotation(ga);
// Add layer to the layer list and update the layer panel
insertBeforeCompass(this.getWwd(), layer);
}
}
public static void main(String[] args) {
ApplicationTemplate.start("WorldWind Annotations", AppFrame.class);
}
}
Here is my (essentially no-op) Clutter Filter:
package gov.nasa.worldwindx.examples;
import java.util.List;
import gov.nasa.worldwind.render.Declutterable;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.util.ClutterFilter;
public class SimpleClutterFilter implements ClutterFilter{
@Override
public void apply(DrawContext dc, List<Declutterable> shapes) {
for(Declutterable shape: shapes) {
dc.addOrderedRenderable(shape);
}
}
}
And I also had to update the gov.nasa.worldwind.render.BasicAnnotationRenderer to have the OrderedAnnotations it creates implement Declutterable. (The only change to this inner class was adding isEnableDecluttering and getBounds):
public class OrderedAnnotation implements OrderedRenderable, Declutterable
{
protected Annotation annotation;
protected double eyeDistance;
protected Layer layer;
public OrderedAnnotation(Annotation annotation, double eyeDistance)
{
this.annotation = annotation;
this.eyeDistance = eyeDistance;
}
public OrderedAnnotation(Annotation annotation, Layer layer, double eyeDistance)
{
this.annotation = annotation;
this.eyeDistance = eyeDistance;
this.layer = layer;
}
public double getDistanceFromEye()
{
return this.eyeDistance;
}
public void render(DrawContext dc)
{
OGLStackHandler stackHandler = new OGLStackHandler();
BasicAnnotationRenderer.this.beginDrawAnnotations(dc, stackHandler);
try
{
this.doRender(dc, this);
// Draw as many as we can in a batch to save ogl state switching.
while (dc.peekOrderedRenderables() instanceof OrderedAnnotation)
{
OrderedAnnotation oa = (OrderedAnnotation) dc.pollOrderedRenderables();
this.doRender(dc, oa);
}
}
catch (WWRuntimeException e)
{
Logging.logger().log(Level.SEVERE, "generic.ExceptionWhileRenderingAnnotation", e);
}
catch (Exception e)
{
Logging.logger().log(Level.SEVERE, "generic.ExceptionWhileRenderingAnnotation", e);
}
finally
{
BasicAnnotationRenderer.this.endDrawAnnotations(dc, stackHandler);
}
}
public void pick(DrawContext dc, java.awt.Point pickPoint)
{
OGLStackHandler stackHandler = new OGLStackHandler();
BasicAnnotationRenderer.this.pickSupport.clearPickList();
BasicAnnotationRenderer.this.beginDrawAnnotations(dc, stackHandler);
try
{
this.annotation.setPickSupport(BasicAnnotationRenderer.this.pickSupport);
this.doRender(dc, this);
// Draw as many as we can in a batch to save ogl state switching.
while (dc.peekOrderedRenderables() instanceof OrderedAnnotation)
{
OrderedAnnotation oa = (OrderedAnnotation) dc.pollOrderedRenderables();
oa.annotation.setPickSupport(BasicAnnotationRenderer.this.pickSupport);
this.doRender(dc, oa);
}
}
catch (WWRuntimeException e)
{
Logging.logger().log(Level.SEVERE, "generic.ExceptionWhilePickingAnnotation", e);
}
catch (Exception e)
{
Logging.logger().log(Level.SEVERE, "generic.ExceptionWhilePickingAnnotation", e);
}
finally
{
BasicAnnotationRenderer.this.endDrawAnnotations(dc, stackHandler);
BasicAnnotationRenderer.this.pickSupport.resolvePick(dc, pickPoint, this.layer);
BasicAnnotationRenderer.this.pickSupport.clearPickList(); // to ensure entries can be garbage collected
}
}
protected void doRender(DrawContext dc, OrderedAnnotation oa)
{
// Swap the draw context's current layer with that of the ordered annotation
Layer previousCurrentLayer = dc.getCurrentLayer();
try
{
dc.setCurrentLayer(oa.layer);
oa.annotation.renderNow(dc);
}
finally
{
dc.setCurrentLayer(previousCurrentLayer); // restore the original layer
}
}
@Override
public boolean isEnableDecluttering() {
return (annotation instanceof GlobeAnnotation);
}
@Override
public Rectangle2D getBounds(DrawContext dc) {
if(annotation instanceof GlobeAnnotation) {
return ((GlobeAnnotation) annotation).computeBounds(dc);
}
return null;
}
}
First of all;
In setupAnnotations
method, you set alwaysOnTop as true for both GlobeAnnotation objects. This might be the reason.
private void setupAnnotations() {
// Create an AnnotationLayer with lots of annotations
this.layer = new AnnotationLayer();
GlobeAnnotation ga = new GlobeAnnotation("Annotation", Position.fromDegrees(20, -100.9, 1000));
ga.getAttributes().setTextColor(Color.white);
ga.getAttributes().setBackgroundColor(Color.BLACK);
ga.getAttributes().setOpacity(.75f);
**ga.setAlwaysOnTop(true);**
layer.addAnnotation(ga);
ga = new GlobeAnnotation("Annotation", Position.fromDegrees(25, -100.9, 1000));
ga.getAttributes().setTextColor(Color.white);
ga.getAttributes().setBackgroundColor(Color.BLACK);
ga.getAttributes().setOpacity(.75f);
**ga.setAlwaysOnTop(true);**
layer.addAnnotation(ga);
// Add layer to the layer list and update the layer panel
insertBeforeCompass(this.getWwd(), layer);
}
Instead of that, putting annotations that you want to be always on top into separate layer and remaining ones into another layer might be solution by using the links above.