All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.gov.nasa.worldwind.render.airspaces.editor.BasicAirspaceControlPointRenderer Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.render.airspaces.editor;

import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.pick.*;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.render.markers.*;
import gov.nasa.worldwind.util.Logging;

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

// TODO: this renderer largely redundant with MarkerRenderer, and the additional fucntionality here should be
// integrated into MarkerRenderer. There are two key pieces of additional functionality:
// (1) an attribute representing the maximum marker size, and
// (2) the ability to disable the depth test.

/**
 * @author dcollins
 * @version $Id: BasicAirspaceControlPointRenderer.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public class BasicAirspaceControlPointRenderer implements AirspaceControlPointRenderer
{
    private boolean enableLighting;
    private Marker controlPointMarker;
    private Material lightMaterial;
    private Vec4 lightDirection;
    private boolean enableDepthTest;
    // Rendering support.
    private double maxMarkerSize;
    private PickSupport pickSupport = new PickSupport();

    public BasicAirspaceControlPointRenderer(Marker controlPointMarker)
    {
        if (controlPointMarker == null)
        {
            String message = Logging.getMessage("nullValue.MarkerIsNull");
            Logging.logger().severe(message);
            throw new IllegalStateException(message);
        }

        this.enableLighting = true;
        this.controlPointMarker = controlPointMarker;
        this.lightMaterial = Material.WHITE;
        this.lightDirection = new Vec4(1.0, 0.5, 1.0);
        this.enableDepthTest = true;
    }

    public BasicAirspaceControlPointRenderer()
    {
        this(createDefaultMarker());
    }

    public static Marker createDefaultMarker()
    {
        // Create an opaque blue sphere. By default the sphere has a 16 pixel radius, but its radius must be at least
        // 0.1 meters .
        MarkerAttributes attributes = new BasicMarkerAttributes(Material.BLUE, BasicMarkerShape.SPHERE, 1.0, 16, 0.1);
        return new BasicMarker(null, attributes, null);
    }

    public boolean isEnableLighting()
    {
        return this.enableLighting;
    }

    public void setEnableLighting(boolean enable)
    {
        this.enableLighting = enable;
    }

    public Marker getControlPointMarker()
    {
        return controlPointMarker;
    }

    public void setControlPointMarker(Marker marker)
    {
        if (marker == null)
        {
            String message = Logging.getMessage("nullValue.MarkerIsNull");
            Logging.logger().severe(message);
            throw new IllegalStateException(message);
        }

        this.controlPointMarker = marker;
    }

    public Material getLightMaterial()
    {
        return this.lightMaterial;
    }

    public void setLightMaterial(Material material)
    {
        if (material != null)
        {
            String message = Logging.getMessage("nullValue.MaterialIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.lightMaterial = material;
    }

    public Vec4 getLightDirection()
    {
        return this.lightDirection;
    }

    public void setLightDirection(Vec4 direction)
    {
        if (direction != null)
        {
            String message = Logging.getMessage("nullValue.DirectionIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        this.lightDirection = direction;
    }

    public boolean isEnableDepthTest()
    {
        return this.enableDepthTest;
    }

    public void setEnableDepthTest(boolean enable)
    {
        this.enableDepthTest = enable;
    }

    public void render(DrawContext dc, Iterable controlPoints)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalStateException(message);
        }
        if (controlPoints == null)
        {
            String message = Logging.getMessage("nullValue.IterableIsNull");
            Logging.logger().severe(message);
            throw new IllegalStateException(message);
        }

        this.draw(dc, controlPoints);
    }

    public void pick(DrawContext dc, Iterable controlPoints, Point pickPoint,
        Layer layer)
    {
        if (dc == null)
        {
            String message = Logging.getMessage("nullValue.DrawContextIsNull");
            Logging.logger().severe(message);
            throw new IllegalStateException(message);
        }
        if (controlPoints == null)
        {
            String message = Logging.getMessage("nullValue.IterableIsNull");
            Logging.logger().severe(message);
            throw new IllegalStateException(message);
        }

        this.pickSupport.clearPickList();
        this.draw(dc, controlPoints);
        this.pickSupport.resolvePick(dc, pickPoint, layer);
        this.pickSupport.clearPickList(); // to ensure entries can be garbage collected
    }

    //**************************************************************//
    //********************  Control Point Rendering  ***************//
    //**************************************************************//

    protected void draw(DrawContext dc, Iterable controlPoints)
    {
        this.begin(dc);
        try
        {
            this.drawControlPoints(dc, controlPoints);
        }
        finally
        {
            this.end(dc);
        }
    }

    protected void drawControlPoints(DrawContext dc, Iterable controlPoints)
    {
        // Render the control points from back-to front.
        SortedSet sortedPoints = this.sortControlPoints(dc, controlPoints);
        this.drawMarkers(dc, sortedPoints);
    }

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

        if (dc.isPickingMode())
        {
            this.pickSupport.beginPicking(dc);
            gl.glPushAttrib(GL2.GL_CURRENT_BIT | GL2.GL_DEPTH_BUFFER_BIT | GL2.GL_TRANSFORM_BIT);
        }
        else
        {
            gl.glPushAttrib(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_CURRENT_BIT | GL2.GL_DEPTH_BUFFER_BIT | GL2.GL_HINT_BIT
                | GL2.GL_LIGHTING_BIT | GL2.GL_TRANSFORM_BIT);

            gl.glEnable(GL.GL_BLEND);
            gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);

            if (this.isEnableLighting())
            {
                this.setupLighting(dc);
            }

            // Were applying a scale transform on the modelview matrix, so the normal vectors must be re-normalized
            // before lighting is computed. In this case we're scaling by a constant factor, so GL_RESCALE_NORMAL
            // is sufficient and potentially less expensive than GL_NORMALIZE (or computing unique normal vectors
            // for each value of radius). GL_RESCALE_NORMAL was introduced in OpenGL version 1.2.
            gl.glEnable(GL2.GL_NORMALIZE);

            gl.glHint(GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);
        }

        if (this.isEnableDepthTest())
        {
            gl.glEnable(GL.GL_DEPTH_TEST);
        }
        else
        {
            gl.glDisable(GL.GL_DEPTH_TEST);
        }

        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glPushMatrix();
    }

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

        gl.glPopMatrix();

        gl.glPopAttrib();

        if (dc.isPickingMode())
        {
            this.pickSupport.endPicking(dc);
        }
    }

    protected PickSupport getPickSupport()
    {
        return this.pickSupport;
    }

    //**************************************************************//
    //********************  Marker Rendering  **********************//
    //**************************************************************//

    protected void drawMarkers(DrawContext dc, Iterable controlPoints)
    {
        // Compute the maximum marker size as a function of the control points to render.
        this.setMaxMarkerSize(this.computeMaxMarkerSize(controlPoints));

        // Apply the marker attributes.
        if (!dc.isPickingMode())
        {
            if (this.isEnableLighting())
            {
                this.getControlPointMarker().getAttributes().apply(dc);
            }
            else
            {
                float[] compArray = new float[4];
                Color color = this.getControlPointMarker().getAttributes().getMaterial().getDiffuse();
                color.getRGBComponents(compArray);
                GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
                gl.glColor4fv(compArray, 0);
            }
        }

        for (AirspaceControlPoint p : controlPoints)
        {
            this.drawMarker(dc, p);
        }
    }

    protected void drawMarker(DrawContext dc, AirspaceControlPoint controlPoint)
    {
        if (!dc.getView().getFrustumInModelCoordinates().contains(controlPoint.getPoint()))
            return;

        if (dc.isPickingMode())
        {
            java.awt.Color color = dc.getUniquePickColor();
            int colorCode = color.getRGB();
            PickedObject po = new PickedObject(colorCode, controlPoint);
            this.pickSupport.addPickableObject(po);
            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
            gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue());
        }

        Vec4 point = controlPoint.getPoint();
        double radius = this.computeMarkerRadius(dc, this.getControlPointMarker(), point);

        this.getControlPointMarker().render(dc, point, radius);
    }

    protected double getMaxMarkerSize()
    {
        return this.maxMarkerSize;
    }

    protected void setMaxMarkerSize(double size)
    {
        this.maxMarkerSize = size;
    }

    protected double computeMarkerRadius(DrawContext dc, Marker marker, Vec4 point)
    {
        double d = dc.getView().getEyePoint().distanceTo3(point);
        double radius = marker.getAttributes().getMarkerPixels() * dc.getView().computePixelSizeAtDistance(d);

        // Constrain the minimum marker size by the marker attributes.
        if (radius < marker.getAttributes().getMinMarkerSize())
        {
            radius = marker.getAttributes().getMinMarkerSize();
        }

        // Constrain the maximum marker size by the computed maximum size.
        if (this.getMaxMarkerSize() > 0.0)
        {
            if (radius > this.getMaxMarkerSize())
            {
                radius = this.getMaxMarkerSize();
            }
        }

        return radius;
    }

    protected double computeMaxMarkerSize(Iterable controlPoints)
    {
        // Compute the maximum marker size as a fraction of the average distance between control points. This will
        // prevent all but the nearest control points from touching as the view moves away from the airspace.

        double totalDistance = 0.0;
        int count = 0;

        for (AirspaceControlPoint p1 : controlPoints)
        {
            for (AirspaceControlPoint p2 : controlPoints)
            {
                if (p1 != p2)
                {
                    double d = p1.getPoint().distanceTo3(p2.getPoint());
                    totalDistance += d;
                    count++;
                }
            }
        }

        // TODO: this is a function that maps average marker distance to a maximum marker size. The function
        // (f(x) = x/16) is currently hard-coded and should be extracted as a method so it will be clearly defined,
        // and the function may be overridden with a different behavior.
        return (count == 0) ? 0.0 : (totalDistance / (double) count / 16.0);
    }

    //**************************************************************//
    //********************  Rendering Support  *********************//
    //**************************************************************//

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

        float[] modelAmbient = new float[4];
        modelAmbient[0] = 1.0f;
        modelAmbient[1] = 1.0f;
        modelAmbient[2] = 1.0f;
        modelAmbient[3] = 0.0f;

        gl.glEnable(GL2.GL_LIGHTING);
        gl.glLightModelfv(GL2.GL_LIGHT_MODEL_AMBIENT, modelAmbient, 0);
        gl.glLightModeli(GL2.GL_LIGHT_MODEL_LOCAL_VIEWER, GL2.GL_TRUE);
        gl.glLightModeli(GL2.GL_LIGHT_MODEL_TWO_SIDE, GL2.GL_FALSE);
        gl.glShadeModel(GL2.GL_SMOOTH);

        // The alpha value at a vertex is taken only from the diffuse material's alpha channel, without any
        // lighting computations applied. Therefore we specify alpha=0 for all lighting ambient, specular and
        // emission values. This will have no effect on material alpha.
        float[] ambient = new float[4];
        float[] diffuse = new float[4];
        float[] specular = new float[4];
        getLightMaterial().getDiffuse().getRGBColorComponents(diffuse);
        getLightMaterial().getSpecular().getRGBColorComponents(specular);
        ambient[3] = diffuse[3] = specular[3] = 0.0f;

        gl.glEnable(GL2.GL_LIGHT0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, ambient, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, diffuse, 0);
        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, specular, 0);

        // Setup the light as a directional light coming from the viewpoint. This requires two state changes
        // (a) Set the light position as direction x, y, z, and set the w-component to 0, which tells OpenGL this is
        //     a directional light.
        // (b) Invoke the light position call with the identity matrix on the modelview stack. Since the position
        //     is transfomed by the

        Vec4 vec = getLightDirection().normalize3();
        float[] params = new float[4];
        params[0] = (float) vec.x;
        params[1] = (float) vec.y;
        params[2] = (float) vec.z;
        params[3] = 0.0f;

        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glPushMatrix();
        gl.glLoadIdentity();

        gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, params, 0);

        gl.glPopMatrix();
    }

    protected SortedSet sortControlPoints(DrawContext dc,
        Iterable unsortedPoints)
    {
        final Vec4 eyePoint = dc.getView().getEyePoint();

        // Sort control points from lower altitude to upper altitude, then from back to front. This will give priority
        // to the upper altitude control points during picking. We give priority to the upper points, in case the
        // shape has been flattened against the terrain. In this case the lower points may not be movable, therefore
        // the user must be able to select an upper point to raise the shape and fix the problem.

        TreeSet set = new TreeSet(new Comparator()
        {
            public int compare(AirspaceControlPoint p1, AirspaceControlPoint p2)
            {
                double d1 = p1.getPoint().distanceTo3(eyePoint);
                double d2 = p2.getPoint().distanceTo3(eyePoint);
                int alt1 = p1.getAltitudeIndex();
                int alt2 = p2.getAltitudeIndex();

                if (alt2 < alt1)
                {
                    return -1;
                }
                else if (alt2 > alt1)
                {
                    return 1;
                }
                else
                {
                    if (d1 < d2)
                    {
                        return 1;
                    }
                    else if (d1 > d2)
                    {
                        return -1;
                    }
                    else
                    {
                        return 0;
                    }
                }
            }
        });

        for (AirspaceControlPoint p : unsortedPoints)
        {
            set.add(p);
        }

        return set;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy