javageometryjoglworldwind

Worldwind PointPlacemark Pitch


I'm trying to figure out why the setPitch in the PointPlacemarkAttributes does not seem to work correctly.

I believe this JOGL code in PointPlacemark.java is where things are going wrong:

        Double heading = getActiveAttributes().getHeading();
        Double pitch = getActiveAttributes().getPitch();

        // Adjust heading to be relative to globe or screen
        if (heading != null)
        {
            if (AVKey.RELATIVE_TO_GLOBE.equals(this.getActiveAttributes().getHeadingReference()))
                heading = dc.getView().getHeading().degrees - heading;
            else
                heading = -heading;
        }

        // Apply the heading and pitch if specified.
        if (heading != null || pitch != null)
        {
            gl.glTranslated(xscale / 2, yscale / 2, 0);
            if (pitch != null)
                gl.glRotated(pitch, 1, 0, 0);
            if (heading != null)
                gl.glRotated(heading, 0, 0, 1);
            gl.glTranslated(-xscale / 2, -yscale / 2, 0);
        }

        // Scale the unit quad
        gl.glScaled(xscale, yscale, 1);

Here is a simple driver I've been using to play with it:

public class Placemarks extends ApplicationTemplate {
    public static class AppFrame extends ApplicationTemplate.AppFrame {
        public AppFrame() {
            super(true, true, false);

            final RenderableLayer layer = new RenderableLayer();

            PointPlacemark pp = new PointPlacemark(Position.fromDegrees(28, -102, 30000));
            pp.setLabelText("PointPlacemark");
            pp.setLineEnabled(false);
            pp.setAltitudeMode(WorldWind.ABSOLUTE);
            PointPlacemarkAttributes attrs = new PointPlacemarkAttributes();
            attrs.setImageAddress("gov/nasa/worldwindx/examples/images/georss.png");
            attrs.setScale(1.0);
            attrs.setImageOffset(Offset.CENTER);


            attrs.setPitch(45.0);

            pp.setAttributes(attrs);
            layer.addRenderable(pp);

            // Add the layer to the model.
            insertBeforeCompass(getWwd(), layer);
        }
    }

    public static void main(String[] args) {
        ApplicationTemplate.start("WorldWind Placemarks", AppFrame.class);
    }
}

If I set no pitch, it looks fine:

enter image description here

But when I set a pitch of 45 degrees it looks like this:

enter image description here

Which I'm not understanding how it correlates to the value I set. I'd expect it to work like the Compass does in the CompassLayer:

enter image description here

Update

Comment suggested to iterate through pitch values to see how it works. I did that and I'm still not seeing how it is supposed to work. It looks like it is just "cropping" the image horizontally, and not doing anything else. Here is some code:

public class Placemarks extends ApplicationTemplate {
    public static class AppFrame extends ApplicationTemplate.AppFrame {
        public AppFrame() {
            super(true, true, false);

            final RenderableLayer layer = new RenderableLayer();

            PointPlacemark pp = new PointPlacemark(Position.fromDegrees(28, -102, 30000));
            pp.setLabelText("PointPlacemark");
            pp.setLineEnabled(false);
            pp.setAltitudeMode(WorldWind.ABSOLUTE);
            PointPlacemarkAttributes attrs = new PointPlacemarkAttributes();
            attrs.setImageAddress("gov/nasa/worldwindx/examples/images/georss.png");
            attrs.setScale(1.0);
            attrs.setImageOffset(Offset.CENTER);


            pp.setAttributes(attrs);
            layer.addRenderable(pp);

            // Add the layer to the model.
            insertBeforeCompass(getWwd(), layer);

            Thread t = new Thread(new Runnable() {

                @Override
                public void run() {
                    for(double i = 0.0; i<360; i+=.1) {
                        attrs.setPitch(i);


                        System.out.println("Pitch is now "+i);

                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }

                        AppFrame.this.getWwd().redrawNow();
                    }

                }
            });
            t.start();
        }
    }

    public static void main(String[] args) {
        ApplicationTemplate.start("WorldWind Placemarks", AppFrame.class);
    }
}

And a screen recorded GIF:

enter image description here


Solution

  • The problem is that in PointPlacemark.doDrawOrderedRenderable(), the orthographic projection matrix used uses a range of depth values from -1 to 1.

    When the pitch remains at 0, the z coordinates also remain at 0, safely in the middle of this range (actually, there is some slight fudging of this coordinate in WorldWind, but never mind that). As it pitches, of course the z coordinates change, until at 90° all of the y coordinates are 0 while z will go to half of the height of the image. This is why only a slice of the image that falls within the range -1,1 is visible while the rest is clipped.

    That z range is defined by the following code:

    // The image is drawn using a parallel projection.
    osh.pushProjectionIdentity(gl);
    gl.glOrtho(0d, dc.getView().getViewport().width, 0d, dc.getView().getViewport().height, -1d, 1d);
    

    If we examine the equivalent code in CompassLayer, we can see that here they do factor in the scaled icon size (although the comment suggests that perhaps at some earlier iteration, less care had been taken over the z dimension):

    double width = this.getScaledIconWidth();
    double height = this.getScaledIconHeight();
    
    // Load a parallel projection with xy dimensions (viewportWidth, viewportHeight)
    // into the GL projection matrix.
    java.awt.Rectangle viewport = dc.getView().getViewport();
    ogsh.pushProjectionIdentity(gl);
    double maxwh = width > height ? width : height;
    if (maxwh == 0)
        maxwh = 1;
    gl.glOrtho(0d, viewport.width, 0d, viewport.height, -0.6 * maxwh, 0.6 * maxwh);
    

    In this case, the arguments for z (±0.6 * maxwh) use 0.6 presumably as 0.5 plus some margin. The actual geometry is a unit quad, which is translated by half width/height in x/y, scaled and rotated accordingly.

    For PointPlacemark, we can account for the size of the renderable in a similar way. Rearranging the code slightly so that scale computation happens before setting the projection, and adding a maxwh value:

    // Compute the scale
    double xscale;
    Double scale = this.getActiveAttributes().getScale();
    if (scale != null)
        xscale = scale * this.activeTexture.getWidth(dc);
    else
        xscale = this.activeTexture.getWidth(dc);
    
    double yscale;
    if (scale != null)
        yscale = scale * this.activeTexture.getHeight(dc);
    else
        yscale = this.activeTexture.getHeight(dc);
    double maxwh = Math.max(xscale, yscale);
    
    // The image is drawn using a parallel projection.
    osh.pushProjectionIdentity(gl);
    gl.glOrtho(0d, dc.getView().getViewport().width, 0d, dc.getView().getViewport().height, -0.6 * maxwh, 0.6 * maxwh);
    

    Again, 0.6 allows some margin.

    It would probably be perfectly fine to have hardcoded values for the z range, as long as they were large enough for any image we might want to draw but not so large that numerical precision became an issue. Conversely, one could go even further and factor in trig to work out the actual depth needed for a given rotation and image size, but there would not be much to gain by doing so.

    This was indeed a bug with WorldWindJava that has been reported, along with a link here for the fix.