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

src.gov.nasa.worldwind.render.markers.BasicMarkerShape 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.markers;

import gov.nasa.worldwind.Disposable;
import gov.nasa.worldwind.cache.GpuResourceCache;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.util.Logging;

import javax.media.opengl.GL2;
import javax.media.opengl.glu.*;
import javax.media.opengl.glu.gl2.GLUgl2;
import java.util.ArrayList;

/**
 * This is the base class for marker symbols.
 *
 * @author tag
 * @version $Id: BasicMarkerShape.java 1181 2013-02-15 22:27:10Z dcollins $
 */
public class BasicMarkerShape
{
    public static final String SPHERE = "gov.nasa.worldwind.render.markers.Sphere";
    public static final String CUBE = "gov.nasa.worldwind.render.markers.Cube";
    public static final String CONE = "gov.nasa.worldwind.render.markers.Cone";
    public static final String CYLINDER = "gov.nasa.worldwind.render.markers.Cylinder";
    public static final String HEADING_ARROW = "gov.nasa.worldwind.render.markers.HeadingArrow";
    public static final String HEADING_LINE = "gov.nasa.worldwind.render.markers.HeadingLine";
    public static final String ORIENTED_SPHERE = "gov.nasa.worldwind.render.markers.DirectionalSphere";
    public static final String ORIENTED_CUBE = "gov.nasa.worldwind.render.markers.DirectionalCube";
    public static final String ORIENTED_CONE = "gov.nasa.worldwind.render.markers.DirectionalCone";
    public static final String ORIENTED_CYLINDER = "gov.nasa.worldwind.render.markers.DirectionalCylinder";
    public static final String ORIENTED_SPHERE_LINE = "gov.nasa.worldwind.render.markers.DirectionalSphereLine";
    public static final String ORIENTED_CONE_LINE = "gov.nasa.worldwind.render.markers.DirectionalConeLine";
    public static final String ORIENTED_CYLINDER_LINE = "gov.nasa.worldwind.render.markers.DirectionalCylinderLine";

    @SuppressWarnings({"StringEquality"})
    public static MarkerShape createShapeInstance(String shapeType)
    {
        if (shapeType == null)
        {
            String message = Logging.getMessage("nullValue.ShapeType");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // String identity rather than equality is wanted here, to avoid a bunch of unnecessary string compares
        if (shapeType == BasicMarkerShape.SPHERE)
            return new Sphere();
        else if (shapeType == BasicMarkerShape.CUBE)
            return new Cube();
        else if (shapeType == BasicMarkerShape.CONE)
            return new Cone();
        else if (shapeType == BasicMarkerShape.CYLINDER)
            return new Cylinder();
        else if (shapeType == BasicMarkerShape.HEADING_ARROW)
            return new HeadingArrow();
        else if (shapeType == BasicMarkerShape.HEADING_LINE)
            return new HeadingLine();
        else if (shapeType == BasicMarkerShape.ORIENTED_SPHERE)
            return new CompoundShape(BasicMarkerShape.ORIENTED_SPHERE, "Oriented Sphere", new Sphere(),
                new HeadingArrow());
        else if (shapeType == BasicMarkerShape.ORIENTED_CUBE)
        {
            Cube cube = new Cube();
            cube.setApplyOrientation(false);  // Heading arrow shows orientation, do not rotate shape
            return new CompoundShape(BasicMarkerShape.ORIENTED_CUBE, "Oriented Cube", cube, new HeadingArrow(),
                .6);
        }
        else if (shapeType == BasicMarkerShape.ORIENTED_CONE)
        {
            Cone cone = new Cone();
            cone.setApplyOrientation(false); // Heading arrow shows orientation, do not rotate shape
            return new CompoundShape(BasicMarkerShape.ORIENTED_CONE, "Oriented Cone", cone, new HeadingArrow(), 0.6);
        }
        else if (shapeType == BasicMarkerShape.ORIENTED_CYLINDER)
        {
            Cylinder cylinder = new Cylinder();
            cylinder.setApplyOrientation(false);  // Heading arrow shows orientation, do not rotate shape
            return new CompoundShape(BasicMarkerShape.ORIENTED_CYLINDER, "Oriented Cylinder", cylinder,
                new HeadingArrow(), .6);
        }
        else if (shapeType == BasicMarkerShape.ORIENTED_SPHERE_LINE)
            return new CompoundShape(BasicMarkerShape.ORIENTED_SPHERE_LINE, "Oriented Sphere Line", new Sphere(),
                new HeadingLine(), 1);
        else if (shapeType == BasicMarkerShape.ORIENTED_CONE_LINE)
        {
            Cone cone = new Cone();
            cone.setApplyOrientation(false);  // Heading arrow shows orientation, do not rotate shape
            return new CompoundShape(BasicMarkerShape.ORIENTED_CONE_LINE, "Oriented Cone Line", cone,
                new HeadingLine(), 2);
        }
        else if (shapeType == BasicMarkerShape.ORIENTED_CYLINDER_LINE)
        {
            Cylinder cylinder = new Cylinder();
            cylinder.setApplyOrientation(false);  // Heading arrow shows orientation, do not rotate shape
            return new CompoundShape(BasicMarkerShape.ORIENTED_CYLINDER_LINE, "Oriented Cylinder Line", cylinder,
                new HeadingLine(), 2);
        }
        else
            return new Sphere();
    }

    protected static class CompoundShape implements MarkerShape, Disposable
    {
        protected String name;
        protected String shapeType;
        protected ArrayList shapes = new ArrayList(2);
        protected double offset = 0;

        public CompoundShape(String shapeType, String name, MarkerShape shape1, MarkerShape shape2)
        {
            this.name = name;
            this.shapeType = shapeType;
            this.shapes.add(shape1);
            this.shapes.add(shape2);
        }

        public CompoundShape(String shapeType, String name, MarkerShape shape1, MarkerShape shape2, double offset)
        {
            this.name = name;
            this.shapeType = shapeType;
            this.shapes.add(shape1);
            this.shapes.add(shape2);
            this.offset = offset;
        }

        public void dispose()
        {
            for (MarkerShape shape : this.shapes)
            {
                if (shape instanceof Disposable)
                    ((Disposable) shape).dispose();
            }
        }

        public String getName()
        {
            return name;
        }

        public String getShapeType()
        {
            return shapeType;
        }

        public void render(DrawContext dc, Marker marker, Vec4 point, double radius)
        {
            this.shapes.get(0).render(dc, marker, point, radius, false);
            if (this.offset != 0)
            {
                Position pos = dc.getGlobe().computePositionFromPoint(point);
                point = dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(),
                    pos.getElevation() + radius * this.offset);
            }
            this.shapes.get(1).render(dc, marker, point, radius, false);
        }

        public void render(DrawContext dc, Marker marker, Vec4 point, double radius, boolean isRelative)
        {
            this.shapes.get(0).render(dc, marker, point, radius, isRelative);
            if (this.offset != 0)
            {
                Position pos = dc.getGlobe().computePositionFromPoint(point);
                point = dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(),
                    pos.getElevation() + radius * this.offset);
            }
            this.shapes.get(1).render(dc, marker, point, radius, isRelative);
        }
    }

    protected static abstract class Shape implements MarkerShape, Disposable
    {
        protected String name;
        protected String shapeType;
        protected GLUquadric quadric;
        protected boolean isInitialized = false;
        protected Object displayListCacheKey = new Object();
        /** Indicates that the shape must apply heading, pitch, and roll. */
        protected boolean applyOrientation = true;

        abstract protected void doRender(DrawContext dc, Marker marker, Vec4 point, double radius, int[] dlResource);

        abstract protected int drawShape(DrawContext dc, double radius);

        protected void initialize(DrawContext dc)
        {
            this.quadric = dc.getGLU().gluNewQuadric();
            dc.getGLU().gluQuadricDrawStyle(quadric, GLU.GLU_FILL);
            dc.getGLU().gluQuadricNormals(quadric, GLU.GLU_SMOOTH);
            dc.getGLU().gluQuadricOrientation(quadric, GLU.GLU_OUTSIDE);
            dc.getGLU().gluQuadricTexture(quadric, false);
        }

        public void dispose()
        {
            if (this.isInitialized)
            {
                GLU glu = new GLUgl2();
                glu.gluDeleteQuadric(this.quadric);
                this.isInitialized = false;
            }
        }

        public String getName()
        {
            return this.name;
        }

        public String getShapeType()
        {
            return this.shapeType;
        }

        /**
         * Indicates whether or not the shape applies heading, pitch, and roll when it draws itself. Even if this field
         * is {@code true}, the shape may not apply all of the rotations.
         *
         * @return {@code true} if orientation is applied to the rendered shape, {@code false} if not.
         */
        public boolean isApplyOrientation()
        {
            return this.applyOrientation;
        }

        /**
         * Specifies whether or not the shape applies heading, pitch, and roll when it renders.
         *
         * @param applyOrientation {@code true} if the shape must apply heading, pitch, and roll (if they are supported
         *                         by the shape), {@code false} if it the shape must not apply this orientation.
         */
        public void setApplyOrientation(boolean applyOrientation)
        {
            this.applyOrientation = applyOrientation;
        }

        public void render(DrawContext dc, Marker marker, Vec4 point, double radius)
        {
            render(dc, marker, point, radius, true);
        }

        public void render(DrawContext dc, Marker marker, Vec4 point, double radius, boolean isRelative)
        {
            if (!this.isInitialized)
                this.initialize(dc);

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

            if (!isRelative)
            {
                dc.getView().pushReferenceCenter(dc, point);
            }
            else
            {
                gl.glPushMatrix();
                gl.glTranslated(point.x, point.y, point.z);
            }

            int[] dlResource = (int[]) dc.getGpuResourceCache().get(this.displayListCacheKey);
            if (dlResource == null)
                dlResource = this.createDisplayList(dc, radius);

            this.doRender(dc, marker, point, radius, dlResource);
            if (!isRelative)
            {
                dc.getView().popReferenceCenter(dc);
            }
            else
            {
                gl.glPopMatrix();
            }
        }

        /**
         * Compute a direction vector given a point, heading and pitch.
         *
         * @param dc      current draw context
         * @param point   point at which to compute direction vector
         * @param normal  surface normal at {@code point}
         * @param heading desired heading
         * @param pitch   desired pitch
         *
         * @return A vector pointing in the direction of the desired heading and pitch
         */
        protected Vec4 computeOrientationVector(DrawContext dc, Vec4 point, Vec4 normal, Angle heading,
            Angle pitch)
        {
            // To compute rotation of the shape toward the proper heading, find a second point in that direction.
            Globe globe = dc.getGlobe();
            Position pos = globe.computePositionFromPoint(point);
            LatLon p2ll = LatLon.greatCircleEndPosition(pos, heading, Angle.fromDegrees(0.1));
            Vec4 p2 = globe.computePointFromPosition(p2ll.getLatitude(), p2ll.getLongitude(),
                pos.getElevation());

            // Find vector in the direction of the heading
            Vec4 p1p2 = p2.subtract3(point).normalize3();

            // Take cross product of normal vector and heading vector to create an axis around which to apply pitch
            // rotation.
            Vec4 pitchAxis = normal.cross3(p1p2);

            return normal.transformBy3(Matrix.fromAxisAngle(pitch, pitchAxis));
        }

        protected int[] createDisplayList(DrawContext dc, double radius)
        {
            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
            int[] dlResource = new int[] {gl.glGenLists(1), 1};

            int size;
            try
            {
                gl.glNewList(dlResource[0], GL2.GL_COMPILE);
                size = this.drawShape(dc, radius);
                gl.glEndList();
            }
            catch (Exception e)
            {
                gl.glEndList();
                gl.glDeleteLists(dlResource[0], dlResource[1]);
                return null;
            }

            dc.getGpuResourceCache().put(this.displayListCacheKey, dlResource, GpuResourceCache.DISPLAY_LISTS, size);

            return dlResource;
        }
    }

    protected static class Sphere extends Shape
    {
        @Override
        protected void initialize(DrawContext dc)
        {
            super.initialize(dc);

            this.name = "Sphere";
            this.shapeType = BasicMarkerShape.SPHERE;

            this.isInitialized = true;
        }

        protected void doRender(DrawContext dc, Marker marker, Vec4 point, double radius, int[] dlResource)
        {
            // Sphere is symmetric about all axes, so no need to apply heading, pitch, or roll.
            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
            gl.glScaled(radius, radius, radius);
            gl.glCallList(dlResource[0]);
        }

        @Override
        protected int drawShape(DrawContext dc, double radius)
        {
            int slices = 24;
            int stacks = 12;

            dc.getGLU().gluSphere(this.quadric, 1, slices, stacks);

            return slices * stacks * 6 * 4; // num vertices * (vertex + normal float coords)
        }
    }

    /** Cube marker shape. The cube can be oriented using heading, pitch, and roll. */
    protected static class Cube extends Shape
    {
        @Override
        protected void initialize(DrawContext dc)
        {
            super.initialize(dc);

            this.name = "Cube";
            this.shapeType = BasicMarkerShape.CUBE;

            this.isInitialized = true;
        }

        @Override
        protected int drawShape(DrawContext dc, double radius)
        {
            // Vertices of a cube, 2 units on each side, with the center of the bottom face on the origin.
            float[][] v = {{-1f, 1f, 0f}, {-1f, 1f, 2f}, {1f, 1f, 2f}, {1f, 1f, 0f},
                {-1f, -1f, 2f}, {1f, -1f, 2f}, {1f, -1f, 0f}, {-1f, -1f, 0f}};

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

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

            gl.glBegin(GL2.GL_QUADS);

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

            gl.glEnd();

            return (8 + 4) * 3 * 4; // assume 8 verts, 4 normals, all of them 3 float coords
        }

        protected void doRender(DrawContext dc, Marker marker, Vec4 point, double size, int[] dlResource)
        {
            Vec4 normal = dc.getGlobe().computeSurfaceNormalAtPoint(point);

            // This performs the same operation as Vec4.axisAngle() but with a "v2" of <0, 0, 1>.
            // Compute rotation angle
            Angle angle = Angle.fromRadians(Math.acos(normal.z));
            // Compute the direction cosine factors that define the rotation axis
            double A = -normal.y;
            double B = normal.x;
            double L = Math.sqrt(A * A + B * B);

            // Rotate the cube so that one of the faces points north
            Position position = dc.getGlobe().computePositionFromPoint(point);
            Vec4 north = dc.getGlobe().computeNorthPointingTangentAtLocation(position.getLatitude(),
                position.getLongitude());
            Vec4 rotatedY = Vec4.UNIT_NEGATIVE_Y.transformBy3(Matrix.fromAxisAngle(angle, A / L, B / L, 0));
            Angle northAngle = rotatedY.angleBetween3(north);

            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
            gl.glRotated(angle.degrees, A / L, B / L, 0);  // rotate cube normal to globe

            gl.glRotated(northAngle.degrees, 0, 0, 1); // rotate to face north

            // Apply heading, pitch, and roll
            if (this.isApplyOrientation())
            {
                if (marker.getHeading() != null)
                    gl.glRotated(-marker.getHeading().degrees, 0, 0, 1);
                if (marker.getPitch() != null)
                    gl.glRotated(marker.getPitch().degrees, 1, 0, 0);
                if (marker.getRoll() != null)
                    gl.glRotated(marker.getRoll().degrees, 0, 0, 1);
            }

            gl.glScaled(size, size, size);
            gl.glCallList(dlResource[0]);
        }
    }

    /** A cone marker shape. The cone can be oriented using heading and pitch. */
    protected static class Cone extends Shape
    {
        @Override
        protected void initialize(DrawContext dc)
        {
            super.initialize(dc);

            this.name = "Cone";
            this.shapeType = BasicMarkerShape.CONE;
            this.isInitialized = true;
        }

        protected void doRender(DrawContext dc, Marker marker, Vec4 point, double size, int[] dlResource)
        {
            // By default, the shape is normal to the globe (0 heading, 0 pitch, 0 roll)
            Vec4 orientation = dc.getGlobe().computeSurfaceNormalAtPoint(point);

            // Heading only applies to cone if pitch is also specified. A heading without pitch spins the cone
            // around its axis. A heading with pitch spins the cone, and then tilts it in the direction of the
            // heading.
            if (this.isApplyOrientation() && marker.getPitch() != null)
            {
                orientation = this.computeOrientationVector(dc, point, orientation,
                    marker.getHeading() != null ? marker.getHeading() : Angle.ZERO,
                    marker.getPitch());
            }

            // This code performs the same operation as Vec4.axisAngle() but with a "v2" of <0, 0, 1>.
            // Compute rotation angle
            Angle angle = Angle.fromRadians(Math.acos(orientation.z));
            // Compute the direction cosine factors that define the rotation axis
            double A = -orientation.y;
            double B = orientation.x;
            double L = Math.sqrt(A * A + B * B);

            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
            gl.glRotated(angle.degrees, A / L, B / L, 0);  // rotate shape to proper heading and pitch

            gl.glScaled(size, size, size);                 // scale
            gl.glCallList(dlResource[0]);
        }

        @Override
        protected int drawShape(DrawContext dc, double radius)
        {
            int slices = 20;
            int stacks = 20;
            int loops = 2;

            dc.getGLU().gluQuadricOrientation(this.quadric, GLU.GLU_OUTSIDE);
            dc.getGLU().gluCylinder(this.quadric, 1d, 0d, 2d, slices, (int) (2 * (Math.sqrt(stacks)) + 1));
            dc.getGLU().gluDisk(this.quadric, 0d, 1d, slices, loops);

            return (slices + stacks + slices * loops) * 6 * 4; // num vertices * (vertex + normal floats) * float size
        }
    }

    /** A cylinder marker shape. The cylinder can be oriented using heading and pitch. */
    protected static class Cylinder extends Shape
    {
        @Override
        protected void initialize(DrawContext dc)
        {
            super.initialize(dc);

            this.name = "Cylinder";
            this.shapeType = BasicMarkerShape.CYLINDER;
            this.isInitialized = true;
        }

        protected void doRender(DrawContext dc, Marker marker, Vec4 point, double size, int[] dlResource)
        {
            Vec4 orientation = dc.getGlobe().computeSurfaceNormalAtPoint(point);

            // Heading only applies to cylinder if pitch is also specified. A heading without pitch spins the cylinder
            // around its axis. A heading with pitch spins the cylinder, and then tilts it in the direction of the
            // heading.
            if (this.isApplyOrientation() && marker.getPitch() != null)
            {
                orientation = this.computeOrientationVector(dc, point, orientation,
                    marker.getHeading() != null ? marker.getHeading() : Angle.ZERO,
                    marker.getPitch());
            }

            // This performs the same operation as Vec4.axisAngle() but with a "v2" of <0, 0, 1>.
            // Compute rotation angle
            Angle angle = Angle.fromRadians(Math.acos(orientation.z));
            // Compute the direction cosine factors that define the rotation axis
            double A = -orientation.y;
            double B = orientation.x;
            double L = Math.sqrt(A * A + B * B);

            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
            gl.glRotated(angle.degrees, A / L, B / L, 0);  // rotate to proper heading and pitch

            gl.glScaled(size, size, size);                 // scale
            gl.glCallList(dlResource[0]);
        }

        @Override
        protected int drawShape(DrawContext dc, double radius)
        {
            int slices = 20;
            int stacks = 1;
            int loops = 1;

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

            glu.gluCylinder(quadric, 1d, 1d, 2d, slices, (int) (2 * (Math.sqrt(stacks)) + 1));
            glu.gluDisk(quadric, 0d, 1d, slices, loops);
            gl.glTranslated(0, 0, 2);
            glu.gluDisk(quadric, 0d, 1d, slices, loops);
            gl.glTranslated(0, 0, -2);

            return slices * 2 * 6 * 4; // top and bottom vertices and normals, assume float coords (4 bytes)
        }
    }

    /** A line that indicates heading. This shape indicates heading; it ignores pitch and roll. */
    protected static class HeadingLine extends Shape
    {
        @Override
        protected void initialize(DrawContext dc)
        {
            super.initialize(dc);

            this.name = "Heading Line";
            this.shapeType = BasicMarkerShape.HEADING_LINE;
            this.isInitialized = true;
        }

        protected void doRender(DrawContext dc, Marker marker, Vec4 point, double size, int[] dlResource)
        {
            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
            MarkerAttributes attrs = marker.getAttributes();

            if (marker.getHeading() == null)
                return;

            // Apply heading material if different from marker's
            if (!dc.isPickingMode() && attrs.getHeadingMaterial() != null
                && !attrs.getHeadingMaterial().equals(attrs.getMaterial()))
            {
                if (attrs.getOpacity() < 1)
                    attrs.getHeadingMaterial().apply(gl, GL2.GL_FRONT, (float) attrs.getOpacity());
                else
                    attrs.getHeadingMaterial().apply(gl, GL2.GL_FRONT);
            }

            // To compute rotation of the line axis toward the proper heading, find a second point in that direction.
            Position pos = dc.getGlobe().computePositionFromPoint(point);
            LatLon p2ll = LatLon.greatCircleEndPosition(pos, marker.getHeading(), Angle.fromDegrees(.1));
            Vec4 p2 = dc.getGlobe().computePointFromPosition(p2ll.getLatitude(), p2ll.getLongitude(),
                pos.getElevation());

            // This method then performs the same operation as Vec4.axisAngle() but with a "v2" of <0, 0, 1>.
            Vec4 p1p2 = p2.subtract3(point).normalize3();
            // Compute rotation angle
            Angle directionAngle = Angle.fromRadians(Math.acos(p1p2.z));
            // Compute the direction cosine factors that define the rotation axis
            double A = -p1p2.y;
            double B = p1p2.x;
            double L = Math.sqrt(A * A + B * B);

            gl.glRotated(directionAngle.degrees, A / L, B / L, 0);  // point line toward p2
            double scale = attrs.getHeadingScale() * size;
            gl.glScaled(scale, scale, scale);                       // scale
            gl.glCallList(dlResource[0]);

            // Restore the marker material if the heading material was applied
            if (!dc.isPickingMode() && attrs.getHeadingMaterial() != null
                && !attrs.getHeadingMaterial().equals(attrs.getMaterial()))
                attrs.apply(dc);
        }

        @Override
        protected int drawShape(DrawContext dc, double radius)
        {
            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
            gl.glBegin(GL2.GL_LINE_STRIP);
            gl.glNormal3f(0f, 1f, 0f);
            gl.glVertex3f(0, 0, 0);
            gl.glVertex3f(0, 0, 1);
            gl.glEnd();

            return 3 * 3 * 4; // three vertices and a normal each with 3 float coordinates
        }
    }

    /** An arrow that indicates heading. This shape indicates heading; it ignores pitch and roll. */
    protected static class HeadingArrow extends Shape
    {
        @Override
        protected void initialize(DrawContext dc)
        {
            super.initialize(dc);

            this.name = "Heading Arrow";
            this.shapeType = BasicMarkerShape.HEADING_ARROW;

            this.isInitialized = true;
        }

        protected void doRender(DrawContext dc, Marker marker, Vec4 point, double size, int[] dlResource)
        {
            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
            MarkerAttributes attrs = marker.getAttributes();

            if (marker.getHeading() == null)
                return;

            // Apply heading material if different from marker's
            if (!dc.isPickingMode() && attrs.getHeadingMaterial() != null
                && !attrs.getHeadingMaterial().equals(attrs.getMaterial()))
            {
                if (attrs.getOpacity() < 1)
                    attrs.getHeadingMaterial().apply(gl, GL2.GL_FRONT, (float) attrs.getOpacity());
                else
                    attrs.getHeadingMaterial().apply(gl, GL2.GL_FRONT);
            }

            // To compute rotation of the arrow axis toward the proper heading, find a second point in that direction.
            Position pos = dc.getGlobe().computePositionFromPoint(point);
            LatLon p2ll = LatLon.greatCircleEndPosition(pos, marker.getHeading(), Angle.fromDegrees(.1));
            Vec4 p2 = dc.getGlobe().computePointFromPosition(p2ll.getLatitude(), p2ll.getLongitude(),
                pos.getElevation());

            // This method then performs the same operation as Vec4.axisAngle() but with a "v2" of <0, 0, 1>.
            Vec4 p1p2 = p2.subtract3(point).normalize3();
            // Compute rotation angle
            Angle directionAngle = Angle.fromRadians(Math.acos(p1p2.z));
            // Compute the direction cosine factors that define the rotation axis
            double A = -p1p2.y;
            double B = p1p2.x;
            double L = Math.sqrt(A * A + B * B);

            // Compute rotation angle on z (roll) to keep the arrow plane parallel to the ground
            Vec4 horizontalVector = dc.getGlobe().computeSurfaceNormalAtPoint(point).cross3(p1p2);
            Vec4 rotatedX = Vec4.UNIT_X.transformBy3(Matrix.fromAxisAngle(directionAngle, A / L, B / L, 0));
            Angle rollAngle = rotatedX.angleBetween3(horizontalVector);
            // Find out which way to do the roll
            double rollDirection = Math.signum(-horizontalVector.cross3(rotatedX).dot3(p1p2));

            gl.glRotated(directionAngle.degrees, A / L, B / L, 0);  // point arrow toward p2
            gl.glRotated(rollAngle.degrees, 0, 0, rollDirection);   // roll arrow to keep it parallel to the ground
            double scale = attrs.getHeadingScale() * size;
            gl.glScaled(scale, scale, scale);                       // scale
            gl.glCallList(dlResource[0]);

            // Restore the marker material if the heading material was applied
            if (!dc.isPickingMode() && attrs.getHeadingMaterial() != null
                && !attrs.getHeadingMaterial().equals(attrs.getMaterial()))
                attrs.apply(dc);
        }

        @Override
        protected int drawShape(DrawContext dc, double radius)
        {
            GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
            gl.glBegin(GL2.GL_POLYGON);
            gl.glNormal3f(0f, 1f, 0f);
            gl.glVertex3f(-.5f, 0, 0);
            gl.glVertex3f(0, 0, 1);
            gl.glVertex3f(0.5f, 0, 0);
            gl.glVertex3f(-0.5f, 0, 0);
            gl.glEnd();

            return 5 * 3 * 4; // 5 vertices and a normal each with 3 float coordinates
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy