javajoglworldwind

Flickering Annotations in WorldWind Java


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;
}

}

Solution

  • First of all;

    Draw order of PointPlacemarks

    https://forum.worldwindcentral.com/forum/world-wind-java-forums/development-help/13263-layer-priority-order

    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.