WorldWind PointPlacemark Heading
Asked Answered
I

2

9

In NASA WorldWind Java, I'm using PointPlacemark to represent an image because it stays the same size regardless of zoom level. The problem is that I want to set the heading on the Point Placemark and have it stay on that compass heading even when the camera is tilted. It works exactly as I want when viewing an untilted globe, but when I tilt, the placemark continues to face the screen instead of tilting with the globe, which causes it to act strange.

Here is a GIF illustrating what I'm seeing: https://giphy.com/embed/3o7WIqZUceR8xh6BOg

I would like the Point Placemark Image to stay on a heading relative to the globe, even when tilted -- so the image essentially is "flattened" as the view is tilted, while still remaining the same size regardless of zoom level.

Here is a code snippet that I'm using. I am setting attrs.setHeadingReference(AVKey.RELATIVE_TO_GLOBE); on the associated PointPlacemarkAttributes. In this example, I am setting the heading to 135 degrees.

import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.render.Offset;
import gov.nasa.worldwind.render.PointPlacemark;
import gov.nasa.worldwind.render.PointPlacemarkAttributes;

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("Airplane");
            pp.setLineEnabled(false);
            pp.setAltitudeMode(WorldWind.ABSOLUTE);
            PointPlacemarkAttributes attrs = new PointPlacemarkAttributes();
            attrs.setImageAddress("images/airplane.png");
            attrs.setScale(0.05);
            attrs.setImageOffset(Offset.CENTER);

            //Point to 135.0
            attrs.setHeading(135.0);
            attrs.setHeadingReference(AVKey.RELATIVE_TO_GLOBE);

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

}

I've also played with using a Polygon with a Texture applied to it. The way it is oriented is what I'm looking for -- except I want the icon to remain the same size regardless of zoom level (like what the PointPlacemark does).

Here is a GIF illustrating what I'm seeing when using a Polygon. Note how it acts when the globe is tilted: https://giphy.com/embed/xThta4USlDzd8Ii5ZS

Here is the source I'm using for the Polygon:

import java.awt.geom.AffineTransform;
import java.util.Arrays;
import java.util.List;

import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.render.BasicShapeAttributes;
import gov.nasa.worldwind.render.Polygon;

public class TexturedPolygon extends ApplicationTemplate {

    public static Polygon createPolygonTexturedImage(String filePath, Position pos, double heading, double scale) {

        double offsetDist = 1.0D * scale;

        Position p1 = Position.fromDegrees(pos.getLatitude().addDegrees(-offsetDist).getDegrees(),
                pos.getLongitude().addDegrees(-offsetDist).getDegrees(), pos.getAltitude());
        Position p2 = Position.fromDegrees(pos.getLatitude().addDegrees(offsetDist).getDegrees(),
                pos.getLongitude().addDegrees(-offsetDist).getDegrees());
        Position p3 = Position.fromDegrees(pos.getLatitude().addDegrees(offsetDist).getDegrees(),
                pos.getLongitude().addDegrees(offsetDist).getDegrees());
        Position p4 = Position.fromDegrees(pos.getLatitude().addDegrees(-offsetDist).getDegrees(),
                pos.getLongitude().addDegrees(offsetDist).getDegrees());

        double[] points = new double[] { p1.getLatitude().getDegrees(), p1.getLongitude().getDegrees(),
                p2.getLatitude().getDegrees(), p2.getLongitude().getDegrees(), p3.getLatitude().getDegrees(),
                p3.getLongitude().getDegrees(), p4.getLatitude().getDegrees(), p4.getLongitude().getDegrees() };

        double[] transformedPoints = new double[8];
        AffineTransform rotation = new AffineTransform();
        rotation.rotate(Math.toRadians(heading), pos.getLatitude().getDegrees(), pos.getLongitude().getDegrees());
        rotation.transform(points, 0, transformedPoints, 0, 4);

        double altitude = pos.getAltitude();
        p1 = Position.fromDegrees(transformedPoints[0], transformedPoints[1], altitude);
        p2 = Position.fromDegrees(transformedPoints[2], transformedPoints[3], altitude);
        p3 = Position.fromDegrees(transformedPoints[4], transformedPoints[5], altitude);
        p4 = Position.fromDegrees(transformedPoints[6], transformedPoints[7], altitude);

        List<Position> positions = Arrays.asList(p1, p2, p3, p4);
        Polygon polygon = new Polygon(positions);
        polygon.setAltitudeMode(WorldWind.ABSOLUTE);

        BasicShapeAttributes mattr = new BasicShapeAttributes();
        mattr.setDrawOutline(false);
        mattr.setDrawInterior(true);
        polygon.setAttributes(mattr);
        polygon.setTextureImageSource(filePath, new float[] { 0.0F, 0.0F, 1.0F, 0.0F, 1.0F, 1.0F, 0.0F, 1.0F }, 4);

        return polygon;
    }

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

            final RenderableLayer layer = new RenderableLayer();

            Position pos = Position.fromDegrees(28, -102, 30000);
            String url = "images/airplane.png";

            layer.addRenderable(createPolygonTexturedImage(url, pos, 135.0, 1.05));

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

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

}

For completeness sake -- here is the image I'm using as my airplane.png:

So to sum it up, what I'm looking for:

  • A Renderable represented by an Icon Image
  • Icon stays the same size regardless of zoom level
  • Icon stays oriented at a globe compass heading, even when camera view tilted
Iceskate answered 9/2, 2018 at 20:47 Comment(2)
A rough diagram or image of what you're after might things more clear. Have you tried using setPitch()? If you can determine what the angle is between a tangent plane relative to the point on earth where the landmark is placed and the landmarks angle then you could apply that difference to setPitch().Rippy
Thanks for the response -- I added some GIF animations illustrating what I'm seeing and reworked it a bit. I played with setPitch a bit, but I'm unclear how it works or if it would help what I'm trying to doIceskate
U
7

By combining the solution to this question and the CompassLayer logic that ties the screen tilt to the pitch.

Add this method to PointPlacemark.java (Taken from CompassLayer):

protected double computePitch(View view)
{
    if (view == null)
        return 0.0;

    if (!(view instanceof OrbitView))
        return 0.0;

    OrbitView orbitView = (OrbitView) view;
    return orbitView.getPitch().getDegrees();
}

And then in the doDrawOrderedRenderable(DrawContext dc, PickSupport pickCandidates, OrderedPlacemark opm) method, use this logic:

protected void doDrawOrderedRenderable(DrawContext dc, PickSupport pickCandidates, OrderedPlacemark opm)
{
    if (this.isDrawLine(dc, opm))
        this.drawLine(dc, pickCandidates, opm);

    if (this.activeTexture == null)
    {
        if (this.isDrawPoint(dc))
            this.drawPoint(dc, pickCandidates, opm);
        return;
    }

    GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.

    OGLStackHandler osh = new OGLStackHandler();
    try
    {
        if (dc.isPickingMode())
        {
            // Set up to replace the non-transparent texture colors with the single pick color.
            gl.glEnable(GL.GL_TEXTURE_2D);
            gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
            gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_PREVIOUS);
            gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_REPLACE);

            Color pickColor = dc.getUniquePickColor();
            pickCandidates.addPickableObject(this.createPickedObject(dc, pickColor));
            gl.glColor3ub((byte) pickColor.getRed(), (byte) pickColor.getGreen(), (byte) pickColor.getBlue());
        }
        else
        {
            gl.glEnable(GL.GL_TEXTURE_2D);
            Color color = this.getActiveAttributes().getImageColor();
            if (color == null)
                color = PointPlacemarkAttributes.DEFAULT_IMAGE_COLOR;
            gl.glColor4ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue(),
                (byte) color.getAlpha());
        }

        // This was relocated from the check in version.
        // 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.
        // This came from the fix in https://mcmap.net/q/1315052/-worldwind-pointplacemark-pitch
        osh.pushProjectionIdentity(gl);
        gl.glOrtho(0d, dc.getView().getViewport().width, 0d, dc.getView().getViewport().height, -0.6 * maxwh, 0.6 * maxwh);

        // Apply the depth buffer but don't change it (for screen-space shapes).
        if ((!dc.isDeepPickingEnabled()))
            gl.glEnable(GL.GL_DEPTH_TEST);
        gl.glDepthMask(false);

        // Suppress any fully transparent image pixels.
        gl.glEnable(GL2.GL_ALPHA_TEST);
        gl.glAlphaFunc(GL2.GL_GREATER, 0.001f);

        // Adjust depth of image to bring it slightly forward
        double depth = opm.screenPoint.z - (8d * 0.00048875809d);
        depth = depth < 0d ? 0d : (depth > 1d ? 1d : depth);
        gl.glDepthFunc(GL.GL_LESS);
        gl.glDepthRange(depth, depth);

        // The image is drawn using a translated and scaled unit quad.
        // Translate to screen point and adjust to align hot spot.
        osh.pushModelviewIdentity(gl);
        gl.glTranslated(opm.screenPoint.x + this.dx, opm.screenPoint.y + this.dy, 0);

        Double heading = getActiveAttributes().getHeading();
        Double pitch =          this.computePitch(dc.getView());

        // 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);

        if (this.activeTexture.bind(dc))
            dc.drawUnitQuad(activeTexture.getTexCoords());

        gl.glDepthRange(0, 1); // reset depth range to the OGL default

        if (this.mustDrawLabel())
        {
            if (!dc.isPickingMode() || this.isEnableLabelPicking())
                this.drawLabel(dc, pickCandidates, opm);
        }
    }
    finally
    {
        if (dc.isPickingMode())
        {
            gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, OGLUtil.DEFAULT_TEX_ENV_MODE);
            gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, OGLUtil.DEFAULT_SRC0_RGB);
            gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, OGLUtil.DEFAULT_COMBINE_RGB);
        }

        gl.glDisable(GL.GL_TEXTURE_2D);
        osh.pop(gl);
    }
}

It will look like this:

enter image description here

Undercarriage answered 9/2, 2018 at 20:47 Comment(2)
Thanks for taking a look. I agree that the way the compass acts would be exactly what I'm looking for. I posted on the worldwind forum to see if they can help: forum.worldwindcentral.com/forum/world-wind-java-forums/…Iceskate
So with the help of another stackoverflow user the pitch problem has been solved: https://mcmap.net/q/1315052/-worldwind-pointplacemark-pitch I think with that fix it will be easy to tie it in to the pitch of the screen like how the compass works. Will take a look.Iceskate
C
5

What you want to achieve is scale your polygon based on the eye position of the camera, and keep the polygon oriented on the map.

You could try to update your second solution and add a RenderingListener ro update the size of your polygon before rendering:

wwd.addRenderingListener(new RenderingListener()
{
    public void stageChanged(RenderingEvent event)
    {
        if (RenderingEvent.BEFORE_RENDERING.equals(event.getStage())
        {
             if (wwd.getView() != null && wwd.getView().getEyePosition() != null) {
                 // compute distance between eyePosition and object position, and set the scale.
             }

        }
    }
});
Celestyna answered 17/2, 2018 at 10:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.