Worldwind Custom Renderable Picking Issue
Asked Answered
I

3

11

I'm going through this tutorial

Whenever my mouse hovers over the cube created with this code (my version below), the Atmosphere and Stars disappear.

This is how it looks normally:

enter image description here

This is how it looks when I hover over the cube (Look at the atmosphere):

enter image description here

I'm not sure what is going on here.

/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gov.nasa.worldwindx.examples.tutorial;

import gov.nasa.worldwind.Configuration;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.OGLUtil;
import gov.nasa.worldwindx.examples.ApplicationTemplate;

import javax.media.opengl.*;
import java.awt.*;

/**
 * Example of a custom {@link Renderable} that draws a cube at a geographic position. This class shows the simplest
 * possible example of a custom Renderable, while still following World Wind best practices. See
 * http://goworldwind.org/developers-guide/how-to-build-a-custom-renderable/ for a complete description of this
 * example.
 *
 * @author pabercrombie
 * @version $Id: Cube.java 691 2012-07-12 19:17:17Z pabercrombie $
 */
public class Cube extends ApplicationTemplate implements Renderable
{
    /** Geographic position of the cube. */
    protected Position position;
    /** Length of each face, in meters. */
    protected double size;

    /** Support object to help with pick resolution. */
    protected PickSupport pickSupport = new PickSupport();

    // Determined each frame
    protected long frameTimestamp = -1L;
    protected OrderedCube currentFramesOrderedCube;

    /**
     * This class holds the Cube's Cartesian coordinates. An instance of it is added to the scene controller's ordered
     * renderable queue during picking and rendering.
     */
    protected class OrderedCube implements OrderedRenderable
    {
        /** Cartesian position of the cube, computed from
         * {@link gov.nasa.worldwindx.examples.tutorial.Cube#position}. */
        protected Vec4 placePoint;
        /** Distance from the eye point to the cube. */
        protected double eyeDistance;
        /**
         * The cube's Cartesian bounding extent.
         */
        protected Extent extent;

        public double getDistanceFromEye()
        {
            return this.eyeDistance;
        }

        public void pick(DrawContext dc, Point pickPoint)
        {
            // Use same code for rendering and picking.
            this.render(dc);
        }

        public void render(DrawContext dc)
        {
            Cube.this.drawOrderedRenderable(dc, Cube.this.pickSupport);
        }
    }

    public Cube(Position position, double sizeInMeters)
    {
        this.position = position;
        this.size = sizeInMeters;
    }

    public void render(DrawContext dc)
    {
        // Render is called twice, once for picking and once for rendering. In both cases an OrderedCube is added to
        // the ordered renderable queue.

        OrderedCube orderedCube = this.makeOrderedRenderable(dc);

        if (orderedCube.extent != null)
        {
            if (!this.intersectsFrustum(dc, orderedCube))
                return;

            // If the shape is less that a pixel in size, don't render it.
            if (dc.isSmall(orderedCube.extent, 1))
                return;
        }

        // Add the cube to the ordered renderable queue. The SceneController sorts the ordered renderables by eye
        // distance, and then renders them back to front.
        dc.addOrderedRenderable(orderedCube);
    }

    /**
     * Determines whether the cube intersects the view frustum.
     *
     * @param dc the current draw context.
     *
     * @return true if this cube intersects the frustum, otherwise false.
     */
    protected boolean intersectsFrustum(DrawContext dc, OrderedCube orderedCube)
    {
        if (dc.isPickingMode())
            return dc.getPickFrustums().intersectsAny(orderedCube.extent);

        return dc.getView().getFrustumInModelCoordinates().intersects(orderedCube.extent);
    }

    /**
     * Compute per-frame attributes, and add the ordered renderable to the ordered renderable list.
     *
     * @param dc Current draw context.
     */
    protected OrderedCube makeOrderedRenderable(DrawContext dc)
    {
        // This method is called twice each frame: once during picking and once during rendering. We only need to
        // compute the placePoint, eye distance and extent once per frame, so check the frame timestamp to see if
        // this is a new frame. However, we can't use this optimization for 2D continuous globes because the
        // Cartesian coordinates of the cube are different for each 2D globe drawn during the current frame.

        if (dc.getFrameTimeStamp() != this.frameTimestamp || dc.isContinuous2DGlobe())
        {
            OrderedCube orderedCube = new OrderedCube();

            // Convert the cube's geographic position to a position in Cartesian coordinates. If drawing to a 2D
            // globe ignore the shape's altitude.
            if (dc.is2DGlobe())
            {
                orderedCube.placePoint = dc.getGlobe().computePointFromPosition(this.position.getLatitude(),
                    this.position.getLongitude(), 0);
            }
            else
            {
                orderedCube.placePoint = dc.getGlobe().computePointFromPosition(this.position);
            }

            // Compute the distance from the eye to the cube's position.
            orderedCube.eyeDistance = dc.getView().getEyePoint().distanceTo3(orderedCube.placePoint);

            // Compute a sphere that encloses the cube. We'll use this sphere for intersection calculations to determine
            // if the cube is actually visible.
            orderedCube.extent = new Sphere(orderedCube.placePoint, Math.sqrt(3.0) * this.size / 2.0);

            // Keep track of the timestamp we used to compute the ordered renderable.
            this.frameTimestamp = dc.getFrameTimeStamp();
            this.currentFramesOrderedCube = orderedCube;

            return orderedCube;
        }
        else
        {
            return this.currentFramesOrderedCube;
        }
    }

    /**
     * Set up drawing state, and draw the cube. This method is called when the cube is rendered in ordered rendering
     * mode.
     *
     * @param dc Current draw context.
     */
    protected void drawOrderedRenderable(DrawContext dc, PickSupport pickCandidates)
    {
        this.beginDrawing(dc);
        try
        {
            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
            if (dc.isPickingMode())
            {
                Color pickColor = dc.getUniquePickColor();
                pickCandidates.addPickableObject(pickColor.getRGB(), this, this.position);
                gl.glColor3ub((byte) pickColor.getRed(), (byte) pickColor.getGreen(), (byte) pickColor.getBlue());
            }

            // Render a unit cube and apply a scaling factor to scale the cube to the appropriate size.
            gl.glScaled(this.size, this.size, this.size);
            this.drawUnitCube(dc);
        }
        finally
        {
            this.endDrawing(dc);
        }
    }

    /**
     * Setup drawing state in preparation for drawing the cube. State changed by this method must be restored in
     * endDrawing.
     *
     * @param dc Active draw context.
     */
    protected void beginDrawing(DrawContext dc)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.

        int attrMask = GL2.GL_CURRENT_BIT | GL2.GL_COLOR_BUFFER_BIT;

        gl.glPushAttrib(attrMask);

        if (!dc.isPickingMode())
        {
            dc.beginStandardLighting();
            gl.glEnable(GL.GL_BLEND);
            OGLUtil.applyBlending(gl, false);

            // Were applying a scale transform on the modelview matrix, so the normal vectors must be re-normalized
            // before lighting is computed.
            gl.glEnable(GL2.GL_NORMALIZE);
        }

        // Multiply the modelview matrix by a surface orientation matrix to set up a local coordinate system with the
        // origin at the cube's center position, the Y axis pointing North, the X axis pointing East, and the Z axis
        // normal to the globe.
        gl.glMatrixMode(GL2.GL_MODELVIEW);

        Matrix matrix = dc.getGlobe().computeSurfaceOrientationAtPosition(this.position);
        matrix = dc.getView().getModelviewMatrix().multiply(matrix);

        double[] matrixArray = new double[16];
        matrix.toArray(matrixArray, 0, false);
        gl.glLoadMatrixd(matrixArray, 0);
    }

    /**
     * Restore drawing state changed in beginDrawing to the default.
     *
     * @param dc Active draw context.
     */
    protected void endDrawing(DrawContext dc)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.

        if (!dc.isPickingMode())
            dc.endStandardLighting();

        gl.glPopAttrib();
    }

    /**
     * Draw a unit cube, using the active modelview matrix to orient the shape.
     *
     * @param dc Current draw context.
     */
    protected void drawUnitCube(DrawContext dc)
    {
        // Vertices of a unit cube, centered on the origin.
        float[][] v = {{-0.5f, 0.5f, -0.5f}, {-0.5f, 0.5f, 0.5f}, {0.5f, 0.5f, 0.5f}, {0.5f, 0.5f, -0.5f},
            {-0.5f, -0.5f, 0.5f}, {0.5f, -0.5f, 0.5f}, {0.5f, -0.5f, -0.5f}, {-0.5f, -0.5f, -0.5f}};

        // Array to group vertices into faces
        int[][] faces = {{0, 1, 2, 3}, {2, 5, 6, 3}, {1, 4, 5, 2}, {0, 7, 4, 1}, {0, 7, 6, 3}, {4, 7, 6, 5}};

        // Normal vectors for each face
        float[][] n = {{0, 1, 0}, {1, 0, 0}, {0, 0, 1}, {-1, 0, 0}, {0, 0, -1}, {0, -1, 0}};

        // Note: draw the cube in OpenGL immediate mode for simplicity. Real applications should use vertex arrays
        // or vertex buffer objects to achieve better performance.
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
        gl.glBegin(GL2.GL_QUADS);
        try
        {
            for (int i = 0; i < faces.length; i++)
            {
                gl.glNormal3f(n[i][0], n[i][1], n[i][2]);

                for (int j = 0; j < faces[0].length; j++)
                {
                    gl.glVertex3f(v[faces[i][j]][0], v[faces[i][j]][1], v[faces[i][j]][2]);
                }
            }
        }
        finally
        {
            gl.glEnd();
        }
    }

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

            RenderableLayer layer = new RenderableLayer();
            Cube cube = new Cube(Position.fromDegrees(35.0, -120.0, 3000), 100000);
            layer.addRenderable(cube);

            getWwd().getModel().getLayers().add(layer);
        }
    }

    public static void main(String[] args)
    {
        Configuration.setValue(AVKey.INITIAL_LATITUDE, 35.0);
        Configuration.setValue(AVKey.INITIAL_LONGITUDE, -120.0);
        Configuration.setValue(AVKey.INITIAL_ALTITUDE, 2550000);
        Configuration.setValue(AVKey.INITIAL_PITCH, 45);
        Configuration.setValue(AVKey.INITIAL_HEADING, 45);

        ApplicationTemplate.start("World Wind Custom Renderable Tutorial", AppFrame.class);
    }
}
Ideology answered 18/4, 2016 at 15:52 Comment(7)
Haven't quite got the answer yet, but the difference with mouse over vs not is that the DrawContext is in picking mode (i.e. dc.isPickingMode is true). I guess that somehow affects the SkyGradientLayer rendering. Maybe that can help you debug, meanwhile - I can reproduce the issue. I'll try to figure it out when I have some time.Vaccaro
Interesting further comment - this happens for the Cube class bundled in the worldwindx.jar too. It's a bit hard to repro, as their cube is much smaller in scale than yours, but if you set the right viewing angle (inclination?) you can do it. I'm still digging into why it occurs.Vaccaro
Thanks for digging into this -- the Cube class I used is really the same as what is in the worldwindx.jar just making the cube larger to emphasize the problem. I am befuddled at what is going on here.Ideology
Are you going to use picking feature? Because I think it could be disabled if you don't want to use it.Dangle
Yes, I'm hoping to -- that is the reason I noticed the issue in the first place. I've got another application that I'm developing that uses WorldWind and custom renderables that has this problem -- that's when I noticed this issue also occurs in their Cube example.Ideology
I think your attrMask misses states. What happens if you push with attrMask GL_ALL_ATTRIB_BITS? (And use OpenGL types, in this case GLbitfield)Content
I've played around with this but haven't seem to find the magic configurationIdeology
I
1

I ended up getting an answer on the worldwind forums: http://forum.worldwindcentral.com/showthread.php?46115-Worldwind-Custom-Renderable-Picking-Issue&p=125173

Add a gl push matrix call in the begin drawing method:

protected void beginDrawing(DrawContext dc)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.

        int attrMask = GL2.GL_CURRENT_BIT | GL2.GL_COLOR_BUFFER_BIT;

        gl.glPushAttrib(attrMask);

        if (!dc.isPickingMode())
        {
            dc.beginStandardLighting();
            gl.glEnable(GL.GL_BLEND);
            OGLUtil.applyBlending(gl, false);

            // Were applying a scale transform on the modelview matrix, so the normal vectors must be re-normalized
            // before lighting is computed.
            gl.glEnable(GL2.GL_NORMALIZE);
        }

        // Multiply the modelview matrix by a surface orientation matrix to set up a local coordinate system with the
        // origin at the cube's center position, the Y axis pointing North, the X axis pointing East, and the Z axis
        // normal to the globe.
        gl.glPushMatrix();
        gl.glMatrixMode(GL2.GL_MODELVIEW);

        Matrix matrix = dc.getGlobe().computeSurfaceOrientationAtPosition(this.position);
        matrix = dc.getView().getModelviewMatrix().multiply(matrix);

        double[] matrixArray = new double[16];
        matrix.toArray(matrixArray, 0, false);
        gl.glLoadMatrixd(matrixArray, 0);
    }

And additionally, add a gl pop matrix call in the end drawing:

protected void endDrawing(DrawContext dc)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.

        if (!dc.isPickingMode())
            dc.endStandardLighting();
        gl.glPopMatrix();
        gl.glPopAttrib();
    }
Ideology answered 2/5, 2016 at 23:40 Comment(0)
V
1

I have reproduced the problem, both with the tutorial Cube class that is included in worldwind.jar and your Cube class that draws a helpfully larger cube.

I have traced the point at which the atmosphere disappears is when the hidden buffer for the GLCanvas is swapped within the WorldWind render code, so the problem is that for some reason the atmosphere and stars layers are not drawn on the hidden buffer during rendering in picking mode using your code.

I then found that the example with Cylinders included as gov.nasa.worldwindx.examples.Cylinders.class draws pickable 3D shapes (cylinders) on the globe and does not exhibit this problem (there are others - Boxes for example is extremely close to this tutorial).

I think that that the issue is with the implementation of OrderedRenderable in this tutorial example - in the Cylinders example, the actual Cylinder class extends RigidShape which in turn extends AbstractShape, which is the class that actually implements OrderedRenderable. It's definitely a bug. Maybe you can work from the Boxes example to get the functionality you need.

Vaccaro answered 28/4, 2016 at 15:17 Comment(4)
Thanks for all of your time looking into this. I'll take a look at Cylinders and Boxes.Ideology
No problem. It's a shame no-one got a proper explanation for you. I'll edit if I work it outVaccaro
I asked on the worldwind forums and got a working answer and have posted it here. Thanks again!Ideology
@dubdubdubdot Fantastic! Saves me working on it any more :) Glad you got there and it's really good to know what it was (it was bugging me).Vaccaro
I
1

I ended up getting an answer on the worldwind forums: http://forum.worldwindcentral.com/showthread.php?46115-Worldwind-Custom-Renderable-Picking-Issue&p=125173

Add a gl push matrix call in the begin drawing method:

protected void beginDrawing(DrawContext dc)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.

        int attrMask = GL2.GL_CURRENT_BIT | GL2.GL_COLOR_BUFFER_BIT;

        gl.glPushAttrib(attrMask);

        if (!dc.isPickingMode())
        {
            dc.beginStandardLighting();
            gl.glEnable(GL.GL_BLEND);
            OGLUtil.applyBlending(gl, false);

            // Were applying a scale transform on the modelview matrix, so the normal vectors must be re-normalized
            // before lighting is computed.
            gl.glEnable(GL2.GL_NORMALIZE);
        }

        // Multiply the modelview matrix by a surface orientation matrix to set up a local coordinate system with the
        // origin at the cube's center position, the Y axis pointing North, the X axis pointing East, and the Z axis
        // normal to the globe.
        gl.glPushMatrix();
        gl.glMatrixMode(GL2.GL_MODELVIEW);

        Matrix matrix = dc.getGlobe().computeSurfaceOrientationAtPosition(this.position);
        matrix = dc.getView().getModelviewMatrix().multiply(matrix);

        double[] matrixArray = new double[16];
        matrix.toArray(matrixArray, 0, false);
        gl.glLoadMatrixd(matrixArray, 0);
    }

And additionally, add a gl pop matrix call in the end drawing:

protected void endDrawing(DrawContext dc)
    {
        GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.

        if (!dc.isPickingMode())
            dc.endStandardLighting();
        gl.glPopMatrix();
        gl.glPopAttrib();
    }
Ideology answered 2/5, 2016 at 23:40 Comment(0)
M
0

move

if (!dc.isPickingMode())
   dc.endStandardLighting();

from

protected void endDrawing(DrawContext dc)

after

gl.glLoadMatrixd(matrixArray, 0);

in

protected void beginDrawing(DrawContext dc)
Makeshift answered 27/4, 2016 at 19:53 Comment(2)
Thanks for taking a look -- maybe I'm doing something wrong, but I'm still having issues. Does this code look right: pastebin.com/Becvr4Gz?Ideology
I can't see why this should work. This bit of code only effects when not picking, but the problem occurs when the OP is picking. I tried a few combinations of relaxing the if (!dc.isPickingMode()) constraints, but couldn't find anything that changed the symptoms. Did you manage to test it and get the atmosphere to remain?Vaccaro

© 2022 - 2024 — McMap. All rights reserved.