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

org.meteoinfo.chart.jogl.Plot3DGL Maven / Gradle / Ivy

There is a newer version: 3.8
Show newest version
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.meteoinfo.chart.jogl;

import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.*;
import com.jogamp.opengl.glu.GLU;
import com.jogamp.opengl.glu.GLUquadric;
import com.jogamp.opengl.glu.GLUtessellator;
import com.jogamp.opengl.math.VectorUtil;
import com.jogamp.opengl.util.awt.AWTGLReadBufferUtil;
import com.jogamp.opengl.util.awt.TextRenderer;
import com.jogamp.opengl.util.gl2.GLUT;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.awt.AWTTextureIO;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.meteoinfo.chart.*;
import org.meteoinfo.chart.axis.Axis;
import org.meteoinfo.chart.graphic.IsosurfaceGraphics;
import org.meteoinfo.chart.graphic.ParticleGraphics;
import org.meteoinfo.chart.graphic.SurfaceGraphics;
import org.meteoinfo.chart.graphic.VolumeGraphics;
import org.meteoinfo.chart.jogl.pipe.Pipe;
import org.meteoinfo.chart.jogl.pipe.PipeShape;
import org.meteoinfo.chart.jogl.tessellator.Primitive;
import org.meteoinfo.chart.jogl.tessellator.TessPolygon;
import org.meteoinfo.chart.plot.GridLine;
import org.meteoinfo.chart.plot.Plot;
import org.meteoinfo.chart.plot.PlotType;
import org.meteoinfo.chart.graphic.GraphicCollection3D;
import org.meteoinfo.chart.render.jogl.RayCastingType;
import org.meteoinfo.chart.render.jogl.VolumeRender;
import org.meteoinfo.chart.shape.TextureShape;
import org.meteoinfo.common.*;
import org.meteoinfo.common.colors.ColorMap;
import org.meteoinfo.data.Dataset;
import org.meteoinfo.geometry.colors.BoundaryNorm;
import org.meteoinfo.geometry.colors.Normalize;
import org.meteoinfo.geometry.graphic.Graphic;
import org.meteoinfo.geometry.graphic.GraphicCollection;
import org.meteoinfo.geometry.legend.*;
import org.meteoinfo.geometry.shape.Shape;
import org.meteoinfo.geometry.shape.*;
import org.meteoinfo.math.meteo.MeteoMath;
import org.meteoinfo.projection.ProjectionInfo;
import org.meteoinfo.projection.ProjectionUtil;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import static com.jogamp.opengl.GL.*;
import static com.jogamp.opengl.GL2ES2.GL_TEXTURE_3D;
import static com.jogamp.opengl.GL2ES2.GL_TEXTURE_WRAP_R;
import static com.jogamp.opengl.GL2ES3.GL_TEXTURE_BASE_LEVEL;

/**
 *
 * @author wyq
 */
public class Plot3DGL extends Plot implements GLEventListener {

    // 
    protected ProjectionInfo projInfo;
    protected boolean sampleBuffers = false;
    protected Color background = Color.white;
    protected boolean doScreenShot;
    protected BufferedImage screenImage;
    protected GL2 gl;
    protected final GLU glu = new GLU();
    protected final GLUT glut = new GLUT();
    protected int startList = 2;
    protected GraphicCollection3D graphics;
    protected Extent3D extent;
    protected Extent3D drawExtent;
    protected ChartText title;
    protected GridLine gridLine;
    protected List legends;
    protected final Axis xAxis;
    protected final Axis yAxis;
    protected final Axis zAxis;
    protected float xmin, xmax = 1.0f, ymin;
    protected float ymax = 1.0f, zmin, zmax = 1.0f;
    protected Transform transform = new Transform();
    protected boolean clipPlane = true;

    protected Color boxColor = Color.getHSBColor(0f, 0f, 0.95f);
    //protected Color lineBoxColor = new Color(220, 220, 220);

    protected boolean boxed, mesh, scaleBox, displayXY, displayZ,
            drawBoundingBox, hideOnDrag, drawBase;

    protected int viewport[] = new int[4];
    protected float mvmatrix[] = new float[16];
    protected float projmatrix[] = new float[16];
    protected Matrix4f viewProjMatrix = new Matrix4f();

    protected float angleX;
    protected float angleY;
    protected float headAngle;
    protected float pitchAngle;
    protected AspectType aspectType = AspectType.AUTO;

    protected TessCallback tessCallback;
    protected int width;
    protected int height;
    protected float tickSpace = 5.0f;
    final protected float lenScale = 0.01f;
    protected Lighting lighting = new Lighting();
    protected boolean antialias;
    protected float dpiScale;   //DPI scale factor
    protected boolean orthographic;
    protected float distance;
    protected GLAutoDrawable drawable;

    private VolumeRender volumeRender;

    // 
    // 
    /**
     * Constructor
     */
    public Plot3DGL() {
        this.projInfo = null;
        this.doScreenShot = false;
        this.legends = new ArrayList<>();
        //this.legends.add(new ChartColorBar(new LegendScheme(ShapeTypes.Polygon, 5)));
        this.xAxis = new Axis();
        //this.xAxis.setLabel("X");
        //this.xAxis.setLabel("Longitude");
        this.xAxis.setTickLength(8);
        this.yAxis = new Axis();
        //this.yAxis.setLabel("Y");
        //this.yAxis.setLabel("Latitude");
        this.yAxis.setTickLength(8);
        this.zAxis = new Axis();
        //this.zAxis.setLabel("Z");
        //this.zAxis.setLabel("Altitude");
        this.zAxis.setTickLength(8);
        this.graphics = new GraphicCollection3D();
        this.hideOnDrag = false;
        this.boxed = true;
        this.gridLine = new GridLine(true);
        //this.displayGrids = true;
        this.drawBase = true;
        this.displayXY = true;
        this.displayZ = true;
        this.drawBoundingBox = false;
        this.antialias = false;
        this.dpiScale = 1;
        this.orthographic = true;
        this.distance = 5.f;
        this.initAngles();
    }

    /**
     * Initialize angles
     */
    public void initAngles() {
        this.angleX = -45.f;
        this.angleY = 45.f;
        this.headAngle = 0.f;
        this.pitchAngle = 0.f;
    }

    // 
    // 
    /**
     * Get projection info
     * @return Projection info
     */
    public ProjectionInfo getProjInfo() {
        return this.projInfo;
    }

    /**
     * Set projection info
     * @param value Projection info
     */
    public void setProjInfo(ProjectionInfo value) {
        this.projInfo = value;
    }

    /**
     * Get is sample buffers or not
     * @return Boolean
     */
    public boolean isSampleBuffers() {
        return this.sampleBuffers;
    }

    /**
     * Set sample buffers or not
     * @param value Boolean
     */
    public void setSampleBuffers(boolean value) {
        this.sampleBuffers = value;
    }

    /**
     * Get background color
     * @return Background color
     */
    public Color getBackground() {
        return this.background;
    }

    /**
     * Set background color
     * @param value Background color
     */
    public void setBackground(Color value) {
        this.background = value;
        if (this.background == Color.black) {
            setForeground(Color.white);
        }
    }

    /**
     * Set foreground color
     * @param value Foreground color
     */
    public void setForeground(Color value) {
        this.boxColor = value;
        this.gridLine.setColor(value);
        this.xAxis.setColor_All(value);
        this.yAxis.setColor_All(value);
        this.zAxis.setColor_All(value);
    }

    /**
     * Get graphics
     * @return The graphics
     */
    public GraphicCollection3D getGraphics() {
        return this.graphics;
    }

    /**
     * Set graphics
     * @param value The graphics
     */
    public void setGraphics(GraphicCollection3D value) {
        this.graphics = value;
    }

    /**
     * Get the number of graphics
     * @return The number of graphics
     */
    public int getGraphicNumber() {
        return this.graphics.size();
    }

    /**
     * Get if do screenshot
     * @return Boolean
     */
    public boolean isDoScreenShot() {
        return this.doScreenShot;
    }

    /**
     * Set if do screenshot
     * @param value Boolean
     */
    public void setDoScreenShot(boolean value) {
        this.doScreenShot = value;
    }

    /**
     * Get screen image
     *
     * @return Screen image
     */
    public BufferedImage getScreenImage() {
        return this.screenImage;
    }

    public Extent3D getExtent() {
        return this.extent;
    }

    /**
     * Set extent
     * @param value Extent
     */
    public void setExtent(Extent3D value) {
        this.extent = value;
    }

    /**
     * Get extent
     *
     * @return Extent
     */
    public Extent3D getDrawExtent() {
        return this.drawExtent;
    }

    /**
     * Set draw extent
     *
     * @param value Extent
     */
    public void setDrawExtent(Extent3D value) {
        this.drawExtent = value;
        xmin = (float) drawExtent.minX;
        xmax = (float) drawExtent.maxX;
        ymin = (float) drawExtent.minY;
        ymax = (float) drawExtent.maxY;
        zmin = (float) drawExtent.minZ;
        zmax = (float) drawExtent.maxZ;
        xAxis.setMinMaxValue(xmin, xmax);
        yAxis.setMinMaxValue(ymin, ymax);
        zAxis.setMinMaxValue(zmin, zmax);
        this.transform.setExtent(this.drawExtent);
    }

    /**
     * Get the extent of all graphics
     * @return The extent of all graphics
     */
    public Extent3D getGraphicExtent() {
        return (Extent3D) this.graphics.getExtent();
    }

    /**
     * Get aspect ratio
     * @return Aspect ratio
     */
    public float getAspectRatio() {
        return (float) this.width / this.height;
    }

    /**
     * Get box color
     *
     * @return Box color
     */
    public Color getBoxColor() {
        return this.boxColor;
    }

    /**
     * Set box color
     *
     * @param value Box color
     */
    public void setBoxColor(Color value) {
        this.boxColor = value;
    }

    /**
     * Get box line color
     *
     * @return Box line color
     *//*
    public Color getLineBoxColor() {
        return this.lineBoxColor;
    }

    *//**
     * Set box line color
     *
     * @param value Box line color
     *//*
    public void setLineBoxColor(Color value) {
        this.lineBoxColor = value;
    }*/

    /**
     * Get if draw base rectangle
     * @return Boolean
     */
    public boolean isDrawBase() {
        return this.drawBase;
    }

    /**
     * Set if draw base rectangle
     * @param value Boolean
     */
    public void setDrawBase(boolean value) {
        this.drawBase = value;
    }

    /**
     * Get if draw bounding box or not
     *
     * @return Boolean
     */
    public boolean isDrawBoundingBox() {
        return this.drawBoundingBox;
    }

    /**
     * Set if draw bounding box or not
     *
     * @param value Boolean
     */
    public void setDrawBoundingBox(boolean value) {
        this.drawBoundingBox = value;
    }

    /**
     * Set display X/Y axis or not
     *
     * @param value Boolean
     */
    public void setDisplayXY(boolean value) {
        this.displayXY = value;
    }

    /**
     * Set display Z axis or not
     *
     * @param value Boolean
     */
    public void setDisplayZ(boolean value) {
        this.displayZ = value;
    }

    /**
     * Get grid line
     *
     * @return Grid line
     */
    public GridLine getGridLine() {
        return this.gridLine;
    }

//    /**
//     * Get display grids or not
//     * @return Boolean
//     */
//    public boolean isDisplayGrids() {
//        return this.displayGrids;
//    }
//
//    /**
//     * Set display grids or not
//     *
//     * @param value Boolean
//     */
//    public void setDisplayGrids(boolean value) {
//        this.displayGrids = value;
//    }

    /**
     * Set display box or not
     *
     * @param value Boolean
     */
    public void setBoxed(boolean value) {
        this.boxed = value;
    }

    /**
     * Get x rotate angle
     *
     * @return X rotate angle
     */
    public float getAngleX() {
        return this.angleX;
    }

    /**
     * Set x rotate angle
     *
     * @param value X rotate angle
     */
    public void setAngleX(float value) {
        this.angleX = value;
    }

    /**
     * Get y rotate angle
     *
     * @return Y rotate angle
     */
    public float getAngleY() {
        return this.angleY;
    }

    /**
     * Set y rotate angle
     *
     * @param value Y rotate angle
     */
    public void setAngleY(float value) {
        this.angleY = value;
    }

    /**
     * Get head angle
     * @return Head angle
     */
    public float getHeadAngle() {
        return this.headAngle;
    }

    /**
     * Set head angle
     * @param value Head angle
     */
    public void setHeadAngle(float value) {
        this.headAngle = value;
    }

    /**
     * Get pitch angle
     * @return Pitch angle
     */
    public float getPitchAngle() {
        return this.pitchAngle;
    }

    /**
     * Set pitch angle
     * @param value Pitch angle
     */
    public void setPitchAngle(float value) {
        this.pitchAngle = value;
    }

    /**
     * Get title
     *
     * @return Title
     */
    public ChartText getTitle() {
        return this.title;
    }

    /**
     * Set title
     *
     * @param value Title
     */
    public void setTitle(ChartText value) {
        this.title = value;
    }

    /**
     * Set title
     *
     * @param text Title text
     */
    public void setTitle(String text) {
        if (this.title == null) {
            this.title = new ChartText(text);
        } else {
            this.title.setText(text);
        }
    }

    /**
     * Get legends
     *
     * @return Legends
     */
    public List getLegends() {
        return this.legends;
    }

    /**
     * Get chart legend
     *
     * @param idx Index
     * @return Chart legend
     */
    public ChartLegend getLegend(int idx) {
        if (this.legends.isEmpty()) {
            return null;
        } else {
            return this.legends.get(idx);
        }
    }

    /**
     * Get chart legend
     *
     * @return Chart legend
     */
    public ChartLegend getLegend() {
        if (this.legends.isEmpty()) {
            return null;
        } else {
            return this.legends.get(this.legends.size() - 1);
        }
    }

    /**
     * Set chart legend
     *
     * @param value Legend
     */
    public void setLegend(ChartLegend value) {
        this.legends.clear();
        this.legends.add(value);
    }

    /**
     * Set legends
     *
     * @param value Legends
     */
    public void setLegends(List value) {
        this.legends = value;
    }

    /**
     * Get x axis
     *
     * @return X axis
     */
    public Axis getXAxis() {
        return this.xAxis;
    }

    /**
     * Get y axis
     *
     * @return Y axis
     */
    public Axis getYAxis() {
        return this.yAxis;
    }

    /**
     * Get z axis
     *
     * @return Z axis
     */
    public Axis getZAxis() {
        return this.zAxis;
    }

    /**
     * Get aspect type
     * @return Aspect type
     */
    public AspectType getAspectType() {
        return this.aspectType;
    }

    /**
     * Set aspect type
     * @param value Aspect type
     */
    public void setAspectType(AspectType value) {
        this.aspectType = value;
        this.transform.setAspectType(this.aspectType);
    }

    /**
     * Get x minimum
     *
     * @return X minimum
     */
    public float getXMin() {
        return this.xmin;
    }

    /**
     * Set minimum x
     *
     * @param value Minimum x
     */
    public void setXMin(float value) {
        this.xmin = value;
        updateExtent();
        this.xAxis.setMinMaxValue(xmin, xmax);
    }

    /**
     * Get x maximum
     *
     * @return X maximum
     */
    public float getXMax() {
        return this.xmax;
    }

    /**
     * Set maximum x
     *
     * @param value Maximum x
     */
    public void setXMax(float value) {
        this.xmax = value;
        updateExtent();
        this.xAxis.setMinMaxValue(xmin, xmax);
    }

    /**
     * Set x minimum and maximum values
     *
     * @param min Minimum value
     * @param max Maximum value
     */
    public void setXMinMax(float min, float max) {
        this.xmin = min;
        this.xmax = max;
        updateExtent();
        this.xAxis.setMinMaxValue(min, max);
    }

    /**
     * Get y minimum
     *
     * @return Y minimum
     */
    public float getYMin() {
        return this.ymin;
    }

    /**
     * Set minimum y
     *
     * @param value Minimum y
     */
    public void setYMin(float value) {
        this.ymin = value;
        updateExtent();
        this.yAxis.setMinMaxValue(ymin, ymax);
    }

    /**
     * Get y maximum
     *
     * @return Y maximum
     */
    public float getYMax() {
        return this.ymax;
    }

    /**
     * Set Maximum y
     *
     * @param value Maximum y
     */
    public void setYMax(float value) {
        this.ymax = value;
        updateExtent();
        this.yAxis.setMinMaxValue(ymin, ymax);
    }

    /**
     * Set y minimum and maximum values
     *
     * @param min Minimum value
     * @param max Maximum value
     */
    public void setYMinMax(float min, float max) {
        this.ymin = min;
        this.ymax = max;
        updateExtent();
        this.yAxis.setMinMaxValue(min, max);
    }

    /**
     * Get z minimum
     *
     * @return Z minimum
     */
    public float getZMin() {
        return this.zmin;
    }

    /**
     * Set minimum z
     *
     * @param value Minimum z
     */
    public void setZMin(float value) {
        this.zmin = value;
        updateExtent();
        this.zAxis.setMinMaxValue(zmin, zmax);
    }

    /**
     * Get z maximum
     *
     * @return Z maximum
     */
    public float getZMax() {
        return this.zmax;
    }

    /**
     * Set maximum z
     *
     * @param value Maximum z
     */
    public void setZMax(float value) {
        this.zmax = value;
        updateExtent();
        this.zAxis.setMinMaxValue(zmin, zmax);
    }

    /**
     * Set z minimum and maximum values
     *
     * @param min Minimum value
     * @param max Maximum value
     */
    public void setZMinMax(float min, float max) {
        this.zmin = min;
        this.zmax = max;
        updateExtent();
        this.zAxis.setMinMaxValue(min, max);
    }

    /**
     * Get lighting set
     *
     * @return Lighting set
     */
    public Lighting getLighting() {
        return this.lighting;
    }

    /**
     * Set lighting set
     *
     * @param value Lighting set
     */
    public void setLighting(Lighting value) {
        this.lighting = value;
    }

    /**
     * Get is antialias or not
     * @return Antialias or not
     */
    public boolean isAntialias() { return this.antialias; }

    /**
     * Set is antialias or not
     * @param value Antialias or not
     */
    public void setAntialias(boolean value) { this.antialias = value; }

    /**
     * Get DPI scale
     * @return DPI scale
     */
    public float getDpiScale() {
        return this.dpiScale;
    }

    /**
     * Set DPI scale
     * @param value DPI scale
     */
    public void setDpiScale(float value) {
        this.dpiScale = value;
    }

    /**
     * Get is orthographic or not
     * @return is orthographic or not
     */
    public boolean isOrthographic() {
        return this.orthographic;
    }

    /**
     * Set orthographic
     * @param value Orthographic or not
     */
    public void setOrthographic(boolean value) {
        this.orthographic = value;
    }

    /**
     * Get camera distance
     * @return Camera distance
     */
    public float getDistance() {
        return this.distance;
    }

    /**
     * Set camera distance
     * @param value Camera distance
     */
    public void setDistance(float value) {
        this.distance = value;
    }

    // 
    // 
    /**
     * Add a legend
     *
     * @param legend The legend
     */
    public void addLegend(ChartLegend legend) {
        this.legends.clear();
        this.legends.add(legend);
    }

    /**
     * Remove a legend
     *
     * @param legend The legend
     */
    public void removeLegend(ChartLegend legend) {
        this.legends.remove(legend);
    }

    /**
     * Remove a legend by index
     *
     * @param idx The legend index
     */
    public void removeLegend(int idx) {
        this.legends.remove(idx);
    }

    /**
     * Get outer position area
     *
     * @param area Whole area
     * @return Position area
     */
    @Override
    public Rectangle2D getOuterPositionArea(Rectangle2D area) {
        Rectangle2D rect = this.getOuterPosition();
        double x = area.getWidth() * rect.getX() + area.getX();
        double y = area.getHeight() * (1 - rect.getHeight() - rect.getY()) + area.getY();
        double w = area.getWidth() * rect.getWidth();
        double h = area.getHeight() * rect.getHeight();
        return new Rectangle2D.Double(x, y, w, h);
    }

    @Override
    public Dataset getDataset() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public void setDataset(Dataset dataset) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public PlotType getPlotType() {
        return PlotType.XYZ;
    }

    @Override
    public void draw(Graphics2D g2, Rectangle2D area) {

    }

    protected void updateExtent() {
        this.extent = new Extent3D(xmin, xmax, ymin, ymax, zmin, zmax);
        this.drawExtent = (Extent3D) this.extent.clone();
        this.transform.setExtent(this.drawExtent);
    }

    /**
     * Set axis tick font
     *
     * @param font Font
     */
    public void setAxisTickFont(Font font) {
        this.xAxis.setTickLabelFont(font);
        this.yAxis.setTickLabelFont(font);
        this.zAxis.setTickLabelFont(font);
    }

    /**
     * Add a graphic
     *
     * @param graphic Graphic
     */
    public void addGraphic(Graphic graphic) {
        this.graphics.add(graphic);
        Extent ex = this.graphics.getExtent();
        if (!ex.is3D()) {
            ex = ex.to3D();
        }
        this.extent = (Extent3D) ex;
        this.setDrawExtent((Extent3D) this.extent.clone());
    }

    /**
     * Add a graphic
     *
     * @param graphic The graphic
     * @param proj The graphic projection
     */
    public void addGraphic(Graphic graphic, ProjectionInfo proj) {
        if (this.projInfo == null || proj.equals(this.projInfo)) {
            addGraphic(graphic);
        } else {
            Graphic nGraphic = ProjectionUtil.projectGraphic(graphic, proj, this.projInfo);
            addGraphic(nGraphic);
        }
    }

    /**
     * Remove a graphic by index
     *
     * @param idx Index
     */
    public void removeGraphic(int idx) {
        this.graphics.remove(idx);
    }

    /**
     * Remove last graphic
     */
    public void removeLastGraphic() {
        this.graphics.remove(this.graphics.size() - 1);
    }

    /**
     * Set auto extent
     */
    public void setAutoExtent() {
    }

    @Override
    public void display(GLAutoDrawable drawable) {
        final GL2 gl = drawable.getGL().getGL2();
        float[] rgba = this.background.getRGBComponents(null);
        gl.glClearColor(rgba[0], rgba[1], rgba[2], rgba[3]);
        gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();

        this.lighting.setPosition(gl);

        gl.glPushMatrix();

        gl.glShadeModel(GL2.GL_SMOOTH);

        gl.glEnable(GL2.GL_BLEND);
        gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA);
        //gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE);

        if (this.antialias) {
            if (this.sampleBuffers)
                gl.glEnable(GL2.GL_MULTISAMPLE);
            else {
                gl.glEnable(GL2.GL_LINE_SMOOTH);
                gl.glHint(GL2.GL_LINE_SMOOTH_HINT, GL2.GL_NICEST);
                gl.glEnable(GL2.GL_POINT_SMOOTH);
                gl.glHint(GL2.GL_POINT_SMOOTH_HINT, GL2.GL_NICEST);
                //gl.glEnable(GL2.GL_POLYGON_SMOOTH);
                //gl.glHint(GL2.GL_POLYGON_SMOOTH_HINT, GL2.GL_NICEST);
            }
        } else {
            if (this.sampleBuffers)
                gl.glDisable(GL2.GL_MULTISAMPLE);
            else {
                gl.glDisable(GL2.GL_LINE_SMOOTH);
                gl.glHint(GL2.GL_LINE_SMOOTH_HINT, GL2.GL_FASTEST);
                gl.glDisable(GL2.GL_POINT_SMOOTH);
                gl.glHint(GL2.GL_POINT_SMOOTH_HINT, GL2.GL_FASTEST);
                //gl.glDisable(GL2.GL_POLYGON_SMOOTH);
                //gl.glHint(GL2.GL_POLYGON_SMOOTH_HINT, GL2.GL_FASTEST);
            }
        }

        //gl.glScalef(scaleX, scaleY, scaleZ);

        gl.glRotatef(angleX, 1.0f, 0.0f, 0.0f);
        gl.glRotatef(angleY, 0.0f, 0.0f, 1.0f);
        if (headAngle != 0) {
            gl.glRotatef(headAngle, 0.0f, 1.0f, 0.0f);
        }

        this.updateMatrix(gl);

        //gl.glColor3f(0.0f, 0.0f, 0.0f);

        //Draw base
        if (this.drawBase) {
            this.drawBase(gl);
        }

        //Draw box
        if (this.boxed) {
            this.drawBox(gl);
        }

        //Draw title
        this.drawTitle();

        this.setLight(gl);

        //Draw graphics
        if (this.clipPlane) {
            enableClipPlane(gl);
        }

        drawGridLine(gl);

        for (int m = 0; m < this.graphics.getNumGraphics(); m++) {
            Graphic graphic = this.graphics.get(m);
            drawGraphics(gl, graphic);
        }

        if (this.clipPlane) {
            disableClipPlane(gl);
        }

        //Draw text
        for (int m = 0; m < this.graphics.getNumGraphics(); m++) {
            Graphic graphic = this.graphics.get(m);
            if (graphic.getNumGraphics() == 1) {
                Shape shape = graphic.getGraphicN(0).getShape();
                if (shape.getShapeType() == ShapeTypes.TEXT) {
                    this.drawText(gl, (ChartText3D) shape);
                }
            } else {
                for (int i = 0; i < graphic.getNumGraphics(); i++) {
                    Shape shape = graphic.getGraphicN(i).getShape();
                    if (shape.getShapeType() == ShapeTypes.TEXT) {
                        this.drawText(gl, (ChartText3D) shape);
                    }
                }
            }
        }

        //Stop lighting
        if (this.lighting.isEnable()) {
            this.lighting.stop(gl);
        }

        //Draw bounding box
        if (this.drawBoundingBox) {
            this.drawBoundingBox(gl);
        }

        //Draw axis
        this.drawAxis(gl);

        //Draw legend
        gl.glPopMatrix();
        this.updateMatrix(gl);
        if (!this.legends.isEmpty()) {
            ChartColorBar legend = (ChartColorBar) this.legends.get(0);
            if (legend.getLegendScheme().getColorMap() == null)
                this.drawLegend(gl, legend);
            else
                this.drawColorbar(gl, legend);
        }

        gl.glFlush();

        //Do screen-shot
        if (this.doScreenShot) {
            AWTGLReadBufferUtil glReadBufferUtil = new AWTGLReadBufferUtil(drawable.getGLProfile(), false);
            this.screenImage = glReadBufferUtil.readPixelsToBufferedImage(drawable.getGL(), true);
            this.doScreenShot = false;
        }
    }

    private void disableClipPlane(GL2 gl) {
        gl.glDisable(GL2.GL_CLIP_PLANE0);
        gl.glDisable(GL2.GL_CLIP_PLANE1);
        gl.glDisable(GL2.GL_CLIP_PLANE2);
        gl.glDisable(GL2.GL_CLIP_PLANE3);
        gl.glDisable(GL2.GL_CLIP_PLANE4);
        gl.glDisable(GL2.GL_CLIP_PLANE5);
    }

    private void enableClipPlane(GL2 gl) {
        float s = 1.01f;
        gl.glClipPlane(GL2.GL_CLIP_PLANE0, new double[]{1, 0, 0, s}, 0);
        gl.glEnable(GL2.GL_CLIP_PLANE0);
        gl.glClipPlane(GL2.GL_CLIP_PLANE1, new double[]{-1, 0, 0, s}, 0);
        gl.glEnable(GL2.GL_CLIP_PLANE1);
        gl.glClipPlane(GL2.GL_CLIP_PLANE2, new double[]{0, -1, 0, s}, 0);
        gl.glEnable(GL2.GL_CLIP_PLANE2);
        gl.glClipPlane(GL2.GL_CLIP_PLANE3, new double[]{0, 1, 0, s}, 0);
        gl.glEnable(GL2.GL_CLIP_PLANE3);
        gl.glClipPlane(GL2.GL_CLIP_PLANE4, new double[]{0, 0, 1, s}, 0);
        gl.glEnable(GL2.GL_CLIP_PLANE4);
        gl.glClipPlane(GL2.GL_CLIP_PLANE5, new double[]{0, 0, -1, s}, 0);
        gl.glEnable(GL2.GL_CLIP_PLANE5);
    }

    protected void setLight(GL2 gl) {
        //Set lighting
        if (this.lighting.isEnable()) {

            this.lighting.start(gl);
            //keep material colors
            //gl.glColorMaterial(GL_FRONT_AND_BACK, GL2.GL_DIFFUSE);
            gl.glColorMaterial(GL2.GL_FRONT_AND_BACK, GL2.GL_AMBIENT_AND_DIFFUSE);
            gl.glEnable(GL2.GL_COLOR_MATERIAL);
            //double side normalize
            gl.glLightModeli(GL2.GL_LIGHT_MODEL_TWO_SIDE, GL2.GL_TRUE);
            //gl.glEnable(GL2.GL_AUTO_NORMAL);
            //gl.glEnable(GL2.GL_NORMALIZE);
            //gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, FloatBuffer.wrap(this.lighting.mat_diffuse));
        }
    }

    /**
     * @param gl The GL context.
     * @param glu The GL unit.
     * @param distance The distance from the screen.
     */
    private void setCamera(GL2 gl, GLU glu, float distance) {
        // Change to projection matrix.
        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();

        // Perspective.
        float widthHeightRatio = (float) this.viewport[2] / (float) this.viewport[3];
        glu.gluPerspective(45, widthHeightRatio, 1, 1000);
        glu.gluLookAt(0, 0, distance, 0, 0, 0, 0, 1, 0);
        //glu.gluLookAt(0, 0, 0, 0, 0, -1, 0, 1, 0);

        // Change back to model view matrix.
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();
    }

    /**
     * Get if clip plane
     * @return Boolean
     */
    public boolean isClipPlane() {
        return this.clipPlane;
    }

    /**
     * Set if clip plane
     * @param value Boolean
     */
    public void setClipPlane(boolean value) {
        this.clipPlane = value;
    }

    /**
     * Draws the base plane. The base plane is the x-y plane.
     *
     * @param g the graphics context to draw.
     * @param x used to retrieve x coordinates of drawn plane from this method.
     * @param y used to retrieve y coordinates of drawn plane from this method.
     */
    protected void drawBase(GL2 gl) {
        float[] rgba = this.gridLine.getColor().getRGBComponents(null);
        gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
        gl.glLineWidth(this.gridLine.getSize() * this.dpiScale);
        gl.glBegin(GL2.GL_LINE_STRIP);
        float xMin = this.transform.transform_x(this.xmin);
        float xMax = this.transform.transform_x(this.xmax);
        float yMin = this.transform.transform_y(this.ymin);
        float yMax = this.transform.transform_y(this.ymax);
        float zMin = this.transform.transform_z(this.zmin);
        gl.glVertex3f(xMin, yMax, zMin);
        gl.glVertex3f(xMin, yMin, zMin);
        gl.glVertex3f(xMax, yMin, zMin);
        gl.glVertex3f(xMax, yMax, zMin);
        gl.glVertex3f(xMin, yMax, zMin);
        /*gl.glVertex3f(-1.0f, 1.0f, -1.0f);
        gl.glVertex3f(-1.0f, -1.0f, -1.0f);
        gl.glVertex3f(1.0f, -1.0f, -1.0f);
        gl.glVertex3f(1.0f, 1.0f, -1.0f);
        gl.glVertex3f(-1.0f, 1.0f, -1.0f);*/
        gl.glEnd();
    }

    private Matrix4f toMatrix(float[] data) {
        Matrix4f matrix4f = new Matrix4f();
        matrix4f.set(data);
        return matrix4f;
    }

    protected void updateMatrix(GL2 gl) {
        gl.glGetIntegerv(GL2.GL_VIEWPORT, viewport, 0);
        gl.glGetFloatv(GL2.GL_MODELVIEW_MATRIX, mvmatrix, 0);
        gl.glGetFloatv(GL2.GL_PROJECTION_MATRIX, projmatrix, 0);
        viewProjMatrix = toMatrix(projmatrix).
                mul(toMatrix(mvmatrix));
    }

    /**
     * Get 3D coordinates from screen 2D coordinates
     * @param x X coordinate
     * @param y Y coordinate
     * @return 3D coordinates
     */
    public Vector3f unProject(float x, float y) {
        if (this.gl == null) {
            return new Vector3f();
        }

        y = viewport[3] - y;
        FloatBuffer buffer = FloatBuffer.allocate(4);
        gl.glReadPixels((int)x, (int)y, 1, 1, gl.GL_DEPTH_COMPONENT, gl.GL_FLOAT, buffer);
        float z = buffer.get();
        float[] out = new float[4];
        glu.gluUnProject(
                x,
                y,
                z,
                mvmatrix, 0,
                projmatrix, 0,
                viewport, 0,
                out, 0
        );

        return new Vector3f(out[0], out[1], out[2]);
    }

    /**
     * Get 3D coordinates from screen 2D coordinates
     * @param x X coordinate
     * @param y Y coordinate
     * @param gl GL2
     * @return 3D coordinates
     */
    public Vector3f unProject(float x, float y, GL2 gl) {
        y = viewport[3] - y;
        FloatBuffer buffer = FloatBuffer.allocate(4);
        gl.glReadPixels((int)x, (int)y, 1, 1, gl.GL_DEPTH_COMPONENT, gl.GL_FLOAT, buffer);
        float z = buffer.get();
        float[] out = new float[4];
        glu.gluUnProject(
                x,
                y,
                z,
                mvmatrix, 0,
                projmatrix, 0,
                viewport, 0,
                out, 0
        );

        return new Vector3f(out[0], out[1], out[2]);
    }

    protected float[] toScreen(float vx, float vy, float vz) {
        //Get screen coordinates
        float coord[] = new float[4];// x, y;// returned xy 2d coords        
        glu.gluProject(vx, vy, vz, mvmatrix, 0, projmatrix, 0, viewport, 0, coord, 0);
        if (viewport[0] != 0)
            coord[0] -= viewport[0];
        if (viewport[1] != 0)
            coord[1] -= viewport[1];

        return coord;
    }

    protected float toScreenLength(float x1, float y1, float z1, float x2, float y2, float z2) {
        float[] coord = toScreen(x1, y1, z1);
        float sx1 = coord[0];
        float sy1 = coord[1];
        coord = toScreen(x2, y2, z2);
        float sx2 = coord[0];
        float sy2 = coord[1];

        return (float) Math.sqrt(Math.pow(sx2 - sx1, 2) + Math.pow(sy2 - sy1, 2));
    }

    protected float toScreenAngle(float x1, float y1, float z1, float x2, float y2, float z2) {
        float[] coord = toScreen(x1, y1, z1);
        float sx1 = coord[0];
        float sy1 = coord[1];
        coord = toScreen(x2, y2, z2);
        float sx2 = coord[0];
        float sy2 = coord[1];

        return (float) MeteoMath.uv2ds(sx2 - sx1, sy2 - sy1)[0];
    }

    protected int getLabelGap(Font font, List labels, double len) {
        TextRenderer textRenderer = new TextRenderer(font);
        int n = labels.size();
        int nn;
        Rectangle2D rect = textRenderer.getBounds("Text".subSequence(0, 4));
        nn = (int) (len / rect.getHeight());
        if (nn == 0) {
            nn = 1;
        }
        return n / nn + 1;
    }

    protected int getLegendTickGap(ChartColorBar legend, double len) {
        if (legend.getTickLabelAngle() != 0) {
            return 1;
        }

        Font font = legend.getTickLabelFont();
        if (this.dpiScale != 1) {
            font = new Font(font.getFontName(), font.getStyle(), (int)(font.getSize() * this.dpiScale));
        }
        TextRenderer textRenderer = new TextRenderer(font);
        int n = legend.getLegendScheme().getBreakNum();
        int nn;
        Rectangle2D rect = textRenderer.getBounds("Text".subSequence(0, 4));
        nn = (int) (len / rect.getHeight());
        if (nn == 0) {
            nn = 1;
        }
        return n / nn + 1;
    }

    protected void drawBox(GL2 gl) {
        float xMin = this.transform.transform_x(this.xmin);
        float xMax = this.transform.transform_x(this.xmax);
        float yMin = this.transform.transform_y(this.ymin);
        float yMax = this.transform.transform_y(this.ymax);
        float zMin = this.transform.transform_z(this.zmin);
        float zMax = this.transform.transform_z(this.zmax);
        float[] rgba = this.gridLine.getColor().getRGBComponents(null);
        gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
        gl.glLineWidth(this.gridLine.getSize() * this.dpiScale);
        if (this.angleY >= 180 && this.angleY < 360) {
            gl.glBegin(GL2.GL_LINE_STRIP);
            gl.glVertex3f(xMin, yMax, zMin);
            gl.glVertex3f(xMin, yMin, zMin);
            gl.glVertex3f(xMin, yMin, zMax);
            gl.glVertex3f(xMin, yMax, zMax);
            gl.glVertex3f(xMin, yMax, zMin);
            gl.glEnd();
        } else {
            gl.glBegin(GL2.GL_LINE_STRIP);
            gl.glVertex3f(xMax, yMax, zMin);
            gl.glVertex3f(xMax, yMin, zMin);
            gl.glVertex3f(xMax, yMin, zMax);
            gl.glVertex3f(xMax, yMax, zMax);
            gl.glVertex3f(xMax, yMax, zMin);
            gl.glEnd();
        }
        if (this.angleY >= 90 && this.angleY < 270) {
            gl.glBegin(GL2.GL_LINE_STRIP);
            gl.glVertex3f(xMin, yMin, zMin);
            gl.glVertex3f(xMax, yMin, zMin);
            gl.glVertex3f(xMax, yMin, zMax);
            gl.glVertex3f(xMin, yMin, zMax);
            gl.glVertex3f(xMin, yMin, zMin);
            gl.glEnd();
        } else {
            gl.glBegin(GL2.GL_LINE_STRIP);
            gl.glVertex3f(xMin, yMax, zMin);
            gl.glVertex3f(xMax, yMax, zMin);
            gl.glVertex3f(xMax, yMax, zMax);
            gl.glVertex3f(xMin, yMax, zMax);
            gl.glVertex3f(xMin, yMax, xMin);
            gl.glEnd();
        }
    }

    protected void drawBoundingBox(GL2 gl) {
        float xMin = this.transform.transform_x(this.xmin);
        float xMax = this.transform.transform_x(this.xmax);
        float yMin = this.transform.transform_y(this.ymin);
        float yMax = this.transform.transform_y(this.ymax);
        float zMin = this.transform.transform_z(this.zmin);
        float zMax = this.transform.transform_z(this.zmax);
        float[] rgba = this.gridLine.getColor().getRGBComponents(null);
        gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
        gl.glLineWidth(this.gridLine.getSize() * this.dpiScale);
        if (this.angleY >= 180 && this.angleY < 360) {
            gl.glBegin(GL2.GL_LINE_STRIP);
            gl.glVertex3f(xMax, yMax, zMin);
            gl.glVertex3f(xMax, yMin, zMin);
            gl.glVertex3f(xMax, yMin, zMax);
            gl.glVertex3f(xMax, yMax, zMax);
            gl.glVertex3f(xMax, yMax, zMin);
            gl.glEnd();
        } else {
            gl.glBegin(GL2.GL_LINE_STRIP);
            gl.glVertex3f(xMin, yMax, zMin);
            gl.glVertex3f(xMin, yMin, zMin);
            gl.glVertex3f(xMin, yMin, zMax);
            gl.glVertex3f(xMin, yMax, zMax);
            gl.glVertex3f(xMin, yMax, zMin);
            gl.glEnd();
        }
        if (this.angleY >= 90 && this.angleY < 270) {
            gl.glBegin(GL2.GL_LINE_STRIP);
            gl.glVertex3f(xMin, yMax, zMin);
            gl.glVertex3f(xMax, yMax, zMin);
            gl.glVertex3f(xMax, yMax, zMax);
            gl.glVertex3f(xMin, yMax, zMax);
            gl.glVertex3f(xMin, yMax, xMin);
            gl.glEnd();
        } else {
            gl.glBegin(GL2.GL_LINE_STRIP);
            gl.glVertex3f(xMin, yMin, zMin);
            gl.glVertex3f(xMax, yMin, zMin);
            gl.glVertex3f(xMax, yMin, zMax);
            gl.glVertex3f(xMin, yMin, zMax);
            gl.glVertex3f(xMin, yMin, zMin);
            gl.glEnd();
        }
    }

    protected void drawXYGridLine(GL2 gl) {
        float xMin = this.transform.transform_x(this.xmin);
        float xMax = this.transform.transform_x(this.xmax);
        float yMin = this.transform.transform_y(this.ymin);
        float yMax = this.transform.transform_y(this.ymax);
        float zMin = this.transform.transform_z(this.zmin);
        float zMax = this.transform.transform_z(this.zmax);
        float[] rgba;
        float x, y, v;
        int skip;
        XAlign xAlign;
        YAlign yAlign;
        Rectangle2D rect;
        float strWidth, strHeight;
        if (this.displayXY) {
            if (this.angleY >= 90 && this.angleY < 270) {
                y = yMax;
            } else {
                y = yMin;
            }

            this.xAxis.updateTickLabels();
            List tlabs = this.xAxis.getTickLabels();
            float axisLen = this.toScreenLength(-1.0f, y, -1.0f, 1.0f, y, -1.0f);
            skip = getLabelGap(this.xAxis.getTickLabelFont(), tlabs, axisLen);
            for (int i = 0; i < this.xAxis.getTickValues().length; i += skip) {
                v = (float) this.xAxis.getTickValues()[i];
                if (v < xmin || v > xmax) {
                    continue;
                }
                v = this.transform.transform_x(v);
                if (i == tlabs.size()) {
                    break;
                }

                //Draw grid line
                if (this.gridLine.isDrawXLine() && (v != -1.0f && v != 1.0f)) {
                    rgba = this.gridLine.getColor().getRGBComponents(null);
                    gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                    gl.glLineWidth(this.gridLine.getSize() * this.dpiScale);
                    gl.glBegin(GL2.GL_LINES);
                    gl.glVertex3f(v, y, zMin);
                    gl.glVertex3f(v, -y, zMin);
                    gl.glEnd();
                    if (this.displayZ && this.boxed) {
                        gl.glBegin(GL2.GL_LINES);
                        gl.glVertex3f(v, -y, zMin);
                        gl.glVertex3f(v, -y, zMax);
                        gl.glEnd();
                    }
                }
            }

            ////////////////////////////////////////////
            //y grid line
            if (this.angleY >= 180 && this.angleY < 360) {
                x = xMax;
            } else {
                x = xMin;
            }

            this.yAxis.updateTickLabels();
            tlabs = this.yAxis.getTickLabels();
            axisLen = this.toScreenLength(x, -1.0f, -1.0f, x, 1.0f, -1.0f);
            skip = getLabelGap(this.yAxis.getTickLabelFont(), tlabs, axisLen);
            for (int i = 0; i < this.yAxis.getTickValues().length; i += skip) {
                v = (float) this.yAxis.getTickValues()[i];
                if (v < ymin || v > ymax) {
                    continue;
                }
                v = this.transform.transform_y(v);
                if (i == tlabs.size()) {
                    break;
                }

                //Draw grid line
                if (this.gridLine.isDrawYLine() && (v != -1.0f && v != 1.0f)) {
                    rgba = this.gridLine.getColor().getRGBComponents(null);
                    gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                    gl.glLineWidth(this.gridLine.getSize() * this.dpiScale);
                    gl.glBegin(GL2.GL_LINES);
                    gl.glVertex3f(x, v, zMin);
                    gl.glVertex3f(-x, v, zMin);
                    gl.glEnd();
                    if (this.displayZ && this.boxed) {
                        gl.glBegin(GL2.GL_LINES);
                        gl.glVertex3f(-x, v, zMin);
                        gl.glVertex3f(-x, v, zMax);
                        gl.glEnd();
                    }
                }
            }
        }
    }

    protected void drawZGridLine(GL2 gl) {
        float xMin = this.transform.transform_x(this.xmin);
        float xMax = this.transform.transform_x(this.xmax);
        float yMin = this.transform.transform_y(this.ymin);
        float yMax = this.transform.transform_y(this.ymax);
        float zMin = this.transform.transform_z(this.zmin);
        float zMax = this.transform.transform_z(this.zmax);
        float[] rgba;
        float x, y, v;
        int skip;
        XAlign xAlign;
        YAlign yAlign;
        Rectangle2D rect;
        float strWidth, strHeight;
        //z axis line
        if (this.angleY < 90) {
            x = xMin;
            y = yMax;
        } else if (this.angleY < 180) {
            x = xMax;
            y = yMax;
        } else if (this.angleY < 270) {
            x = xMax;
            y = yMin;
        } else {
            x = xMin;
            y = yMin;
        }

        this.zAxis.updateTickLabels();
        List tlabs = this.zAxis.getTickLabels();
        float axisLen = this.toScreenLength(x, y, zMin, x, y, zMax);
        skip = getLabelGap(this.zAxis.getTickLabelFont(), tlabs, axisLen);
        for (int i = 0; i < this.zAxis.getTickValues().length; i += skip) {
            v = (float) this.zAxis.getTickValues()[i];
            if (v < zmin || v > zmax) {
                continue;
            }
            v = this.transform.transform_z(v);
            if (i == tlabs.size()) {
                break;
            }

            //Draw grid line
            if (this.gridLine.isDrawZLine() && this.boxed && (v != zMin && v != zMax)) {
                rgba = this.gridLine.getColor().getRGBComponents(null);
                gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                gl.glLineWidth(this.gridLine.getSize() * this.dpiScale);
                gl.glBegin(GL2.GL_LINE_STRIP);
                gl.glVertex3f(x, y, v);
                if (x < 0) {
                    if (y > 0) {
                        gl.glVertex3f(-x, y, v);
                        gl.glVertex3f(-x, -y, v);
                    } else {
                        gl.glVertex3f(x, -y, v);
                        gl.glVertex3f(-x, -y, v);
                    }
                } else {
                    if (y > 0) {
                        gl.glVertex3f(x, -y, v);
                        gl.glVertex3f(-x, -y, v);
                    } else {
                        gl.glVertex3f(-x, y, v);
                        gl.glVertex3f(-x, -y, v);
                    }
                }
                gl.glEnd();
            }
        }
    }

    protected void drawGridLine(GL2 gl) {
        //Draw x/y grid line
        if (this.displayXY) {
            this.drawXYGridLine(gl);
        }

        //Draw z grid line
        if (this.displayZ) {
            this.drawZGridLine(gl);
        }
    }

    protected void drawBoxGrids(GL2 gl) {
        //Draw base
        if (this.drawBase) {
            this.drawBase(gl);
        }

        //Draw box
        if (this.boxed) {
            this.drawBox(gl);
        }

        //Draw grid lines
        this.drawGridLine(gl);
    }

    protected void drawAxis(GL2 gl) {
        float xMin = this.transform.transform_x(this.xmin);
        float xMax = this.transform.transform_x(this.xmax);
        float yMin = this.transform.transform_y(this.ymin);
        float yMax = this.transform.transform_y(this.ymax);
        float zMin = this.transform.transform_z(this.zmin);
        float zMax = this.transform.transform_z(this.zmax);

        gl.glDepthFunc(GL.GL_ALWAYS);

        //Draw axis
        float[] rgba;
        float x, y, v;
        int skip;
        XAlign xAlign;
        YAlign yAlign;
        Rectangle2D rect;
        float strWidth, strHeight;
        if (this.displayXY) {
            //Draw x/y axis lines
            //x axis line
            if (this.angleY >= 90 && this.angleY < 270) {
                y = yMax;
            } else {
                y = yMin;
            }
            rgba = this.xAxis.getLineColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glLineWidth(this.xAxis.getLineWidth() * this.dpiScale);
            gl.glBegin(GL2.GL_LINES);
            gl.glVertex3f(xMin, y, zMin);
            gl.glVertex3f(xMax, y, zMin);
            gl.glEnd();

            //x axis ticks
            float tickLen = this.xAxis.getTickLength() * this.lenScale;
            this.xAxis.updateTickLabels();
            List tlabs = this.xAxis.getTickLabels();
            float axisLen = this.toScreenLength(xMin, y, zMin, xMax, y, zMin);
            skip = getLabelGap(this.xAxis.getTickLabelFont(), tlabs, axisLen);
            float y1 = y > 0 ? y + tickLen : y - tickLen;
            if (this.angleY < 90 || (this.angleY >= 180 && this.angleY < 270)) {
                xAlign = XAlign.LEFT;
            } else {
                xAlign = XAlign.RIGHT;
            }
            if (this.angleX > -120) {
                yAlign = YAlign.TOP;
            } else {
                yAlign = YAlign.BOTTOM;
            }
            strWidth = 0.0f;
            strHeight = 0.0f;
            for (int i = 0; i < this.xAxis.getTickValues().length; i += skip) {
                v = (float) this.xAxis.getTickValues()[i];
                if (v < xmin || v > xmax) {
                    continue;
                }
                v = this.transform.transform_x(v);
                if (i == tlabs.size()) {
                    break;
                }

                //Draw tick line
                rgba = this.xAxis.getLineColor().getRGBComponents(null);
                gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                gl.glLineWidth(this.xAxis.getLineWidth() * this.dpiScale);
                gl.glBegin(GL2.GL_LINES);
                gl.glVertex3f(v, y, zMin);
                gl.glVertex3f(v, y1, zMin);
                gl.glEnd();

                //Draw tick label
                rect = drawString(gl, tlabs.get(i), v, y1, zMin, xAlign, yAlign);
                if (strWidth < rect.getWidth()) {
                    strWidth = (float) rect.getWidth();
                }
                if (strHeight < rect.getHeight()) {
                    strHeight = (float) rect.getHeight();
                }
            }

            //Draw x axis label
            ChartText label = this.xAxis.getLabel();
            if (label != null) {
                strWidth += this.tickSpace;
                float angle = this.toScreenAngle(xMin, y, zMin, xMax, y, zMin);
                angle = y < 0 ? 270 - angle : 90 - angle;
                float yShift = Math.min(-strWidth, -strWidth);
                if (this.angleX <= -120) {
                    yShift = -yShift;
                }
                drawString(gl, label, 0.0f, y1, zMin, XAlign.CENTER, yAlign, angle, 0, yShift);
            }

            ////////////////////////////////////////////
            //y axis line
            if (this.angleY >= 180 && this.angleY < 360) {
                x = xMax;
            } else {
                x = xMin;
            }
            rgba = this.yAxis.getLineColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glLineWidth(this.yAxis.getLineWidth() * this.dpiScale);
            gl.glBegin(GL2.GL_LINES);
            gl.glVertex3f(x, yMin, zMin);
            gl.glVertex3f(x, yMax, zMin);
            gl.glEnd();

            //y axis ticks
            this.yAxis.updateTickLabels();
            tlabs = this.yAxis.getTickLabels();
            axisLen = this.toScreenLength(x, yMin, zMin, x, yMax, zMin);
            skip = getLabelGap(this.yAxis.getTickLabelFont(), tlabs, axisLen);
            tickLen = this.yAxis.getTickLength() * this.lenScale;
            float x1 = x > 0 ? x + tickLen : x - tickLen;
            if (this.angleY < 90 || (this.angleY >= 180 && this.angleY < 270)) {
                xAlign = XAlign.RIGHT;
            } else {
                xAlign = XAlign.LEFT;
            }
            if (this.angleX > -120) {
                yAlign = YAlign.TOP;
            } else {
                yAlign = YAlign.BOTTOM;
            }
            strWidth = 0.0f;
            strHeight = 0.0f;
            for (int i = 0; i < this.yAxis.getTickValues().length; i += skip) {
                v = (float) this.yAxis.getTickValues()[i];
                if (v < ymin || v > ymax) {
                    continue;
                }
                v = this.transform.transform_y(v);
                if (i == tlabs.size()) {
                    break;
                }

                //Draw tick line
                rgba = this.yAxis.getLineColor().getRGBComponents(null);
                gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                gl.glLineWidth(this.yAxis.getLineWidth() * this.dpiScale);
                gl.glBegin(GL2.GL_LINES);
                gl.glVertex3f(x, v, zMin);
                gl.glVertex3f(x1, v, zMin);
                gl.glEnd();

                //Draw tick label
                rect = drawString(gl, tlabs.get(i), x1, v, zMin, xAlign, yAlign);
                if (strWidth < rect.getWidth()) {
                    strWidth = (float) rect.getWidth();
                }
                if (strHeight < rect.getHeight()) {
                    strHeight = (float) rect.getHeight();
                }
            }

            //Draw y axis label
            label = this.yAxis.getLabel();
            if (label != null) {
                strWidth += this.tickSpace;
                float angle = this.toScreenAngle(x, yMin, zMin, x, yMax, xMin);
                angle = x > 0 ? 270 - angle : 90 - angle;
                float yShift = Math.min(-strWidth, -strWidth);
                if (this.angleX <= -120) {
                    yShift = -yShift;
                }
                drawString(gl, label, x1, 0.0f, zMin, XAlign.CENTER, yAlign, angle, 0, yShift);
            }
        }

        //Draw z axis
        if (this.displayZ) {
            //z axis line
            if (this.angleY < 90) {
                x = xMin;
                y = yMax;
            } else if (this.angleY < 180) {
                x = xMax;
                y = yMax;
            } else if (this.angleY < 270) {
                x = xMax;
                y = yMin;
            } else {
                x = xMin;
                y = yMin;
            }
            rgba = this.zAxis.getLineColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glLineWidth(this.zAxis.getLineWidth() * this.dpiScale);
            gl.glBegin(GL2.GL_LINES);
            gl.glVertex3f(x, y, zMin);
            gl.glVertex3f(x, y, zMax);
            gl.glEnd();

            //z axis ticks
            this.zAxis.updateTickLabels();
            List tlabs = this.zAxis.getTickLabels();
            float axisLen = this.toScreenLength(x, y, zMin, x, y, zMax);
            skip = getLabelGap(this.zAxis.getTickLabelFont(), tlabs, axisLen);
            float x1 = x;
            float y1 = y;
            float tickLen = this.zAxis.getTickLength() * this.lenScale;
            if (x < 0) {
                if (y > 0) {
                    y1 += tickLen;
                } else {
                    x1 -= tickLen;
                }
            } else {
                if (y > 0) {
                    x1 += tickLen;
                } else {
                    y1 -= tickLen;
                }
            }
            xAlign = XAlign.RIGHT;
            yAlign = YAlign.CENTER;
            strWidth = 0.0f;
            for (int i = 0; i < this.zAxis.getTickValues().length; i += skip) {
                v = (float) this.zAxis.getTickValues()[i];
                if (v < zmin || v > zmax) {
                    continue;
                }
                v = this.transform.transform_z(v);
                if (i == tlabs.size()) {
                    break;
                }

                //Draw tick line
                rgba = this.zAxis.getLineColor().getRGBComponents(null);
                gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                gl.glLineWidth(this.zAxis.getLineWidth() * this.dpiScale);
                gl.glBegin(GL2.GL_LINES);
                gl.glVertex3f(x, y, v);
                gl.glVertex3f(x1, y1, v);
                gl.glEnd();

                //Draw tick label
                rect = drawString(gl, tlabs.get(i), x1, y1, v, xAlign, yAlign, -this.tickSpace, 0);
                if (strWidth < rect.getWidth()) {
                    strWidth = (float) rect.getWidth();
                }
            }

            //Draw z axis label
            ChartText label = this.zAxis.getLabel();
            if (label != null) {
                float yShift = strWidth + this.tickSpace * 3;
                drawString(gl, label, x1, y1, 0.0f, XAlign.CENTER, YAlign.BOTTOM, 90.f, 0, yShift);
            }
        }
        gl.glDepthFunc(GL2.GL_LEQUAL);
    }

    Rectangle2D drawString(GL2 gl, ChartText text, float vx, float vy, float vz, XAlign xAlign, YAlign yAlign) {
        return drawString(gl, text, vx, vy, vz, xAlign, yAlign, 0, 0);
    }

    Rectangle2D drawString(GL2 gl, ChartText text, float vx, float vy, float vz,
                           XAlign xAlign, YAlign yAlign, float xShift, float yShift) {
        return drawString(gl, text.getText(), text.getFont(), text.getColor(), vx,
                vy, vz, xAlign, yAlign, xShift, yShift);
    }

    Rectangle2D drawString(GL2 gl, String str, Font font, Color color, float vx, float vy, float vz,
            XAlign xAlign, YAlign yAlign) {
        return drawString(gl, str, font, color, vx, vy, vz, xAlign, yAlign, 0, 0);
    }

    Rectangle2D drawString(GL2 gl, String str, Font font, Color color, float vx, float vy, float vz,
            XAlign xAlign, YAlign yAlign, float xShift, float yShift) {
        //Get screen coordinates
        float coord[] = this.toScreen(vx, vy, vz);
        float x = coord[0];
        float y = coord[1];

        //Rendering text string
        TextRenderer textRenderer;
        if (this.dpiScale == 1) {
            textRenderer = new TextRenderer(font, true, true);
        } else {
            textRenderer = new TextRenderer(new Font(font.getFontName(), font.getStyle(),
                    (int)(font.getSize() * (1 + (this.dpiScale - 1) * 0.8))), true, true);
        }
        textRenderer.beginRendering(this.width, this.height);
        textRenderer.setColor(color);
        textRenderer.setSmoothing(true);
        Rectangle2D rect = textRenderer.getBounds(str.subSequence(0, str.length()));
        switch (xAlign) {
            case CENTER:
                x -= rect.getWidth() * 0.5;
                break;
            case RIGHT:
                x -= rect.getWidth();
                break;
        }
        switch (yAlign) {
            case CENTER:
                y -= rect.getHeight() * 0.3;
                break;
            case TOP:
                y -= rect.getHeight();
                break;
        }
        textRenderer.draw(str, (int) (x + xShift), (int) (y + yShift));
        textRenderer.endRendering();

        return rect;
    }

    Rectangle2D drawString(GL2 gl, ChartText text, float vx, float vy, float vz,
            XAlign xAlign, YAlign yAlign, float angle) {
        return drawString(gl, text.getText(), text.getFont(), text.getColor(), vx, vy, vz, xAlign, yAlign, angle,
                (float)text.getXShift(), (float)text.getYShift());
    }

    Rectangle2D drawString(GL2 gl, ChartText text, float vx, float vy, float vz,
            XAlign xAlign, YAlign yAlign, float angle, float xShift, float yShift) {
        return drawString(gl, text.getText(), text.getFont(), text.getColor(), vx, vy,
                vz, xAlign, yAlign, angle, (float)text.getXShift() + xShift, (float)text.getYShift() + yShift);
    }

    Rectangle2D drawString(GL2 gl, String str, Font font, Color color, float vx, float vy, float vz,
            XAlign xAlign, YAlign yAlign, float angle) {
        return drawString(gl, str, font, color, vx, vy, vz, xAlign, yAlign, angle, 0, 0);
    }

    Rectangle2D drawString(GL2 gl, String str, Font font, Color color, float vx, float vy, float vz,
            XAlign xAlign, YAlign yAlign, float angle, float xShift, float yShift) {
        //Get screen coordinates
        float coord[] = this.toScreen(vx, vy, vz);
        float x = coord[0];
        float y = coord[1];

        //Rendering text string
        TextRenderer textRenderer;
        if (this.dpiScale == 1) {
            textRenderer = new TextRenderer(font, true, true);
        } else {
            textRenderer = new TextRenderer(new Font(font.getFontName(), font.getStyle(),
                    (int)(font.getSize() * (1 + (this.dpiScale - 1) * 0.8))), true, true);
        }
        textRenderer.beginRendering(this.width, this.height);
        textRenderer.setColor(color);
        textRenderer.setSmoothing(true);
        Rectangle2D rect = textRenderer.getBounds(str.subSequence(0, str.length()));
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glPushMatrix();
        gl.glTranslatef(x, y, 0.0f);
        if (angle != 0) {
            gl.glRotatef(angle, 0.0f, 0.0f, 1.0f);
        }
        x = 0;
        y = 0;
        switch (xAlign) {
            case CENTER:
                x -= rect.getWidth() * 0.5;
                break;
            case RIGHT:
                x -= rect.getWidth();
                break;
        }
        switch (yAlign) {
            case CENTER:
                y -= rect.getHeight() * 0.5;
                break;
            case TOP:
                y -= rect.getHeight();
                break;
        }
        x += xShift;
        y += yShift;
        textRenderer.draw(str, (int) x, (int) y);
        textRenderer.endRendering();
        textRenderer.dispose();
        gl.glPopMatrix();

        return rect;
    }

    void drawTitle() {
        if (title != null) {
            //Rendering text string
            Font font = title.getFont();
            TextRenderer textRenderer;
            if (this.dpiScale == 1) {
                textRenderer = new TextRenderer(font, true, true);
            } else {
                textRenderer = new TextRenderer(new Font(font.getFontName(), font.getStyle(),
                        (int)(font.getSize() * this.dpiScale)), true, true);
            }
            textRenderer.beginRendering(this.width, this.height);
            textRenderer.setColor(title.getColor());
            textRenderer.setSmoothing(true);
            Rectangle2D rect = textRenderer.getBounds(title.getText().subSequence(0, title.getText().length()));
            float x = (float) (this.width / 2.0f) - (float) rect.getWidth() / 2.0f;
            float y = this.height - (float) rect.getHeight();
            textRenderer.draw(title.getText(), (int) x, (int) y);
            textRenderer.endRendering();
        }
    }

    protected void drawGraphics(GL2 gl, Graphic graphic) {
        boolean lightEnabled = this.lighting.isEnable();
        if (graphic instanceof GraphicCollection3D) {
            boolean usingLight = lightEnabled && ((GraphicCollection3D)graphic).isUsingLight();
            if (lightEnabled && !((GraphicCollection3D)graphic).isUsingLight()) {
                this.lighting.stop(gl);
            }
        }

        if (graphic.getNumGraphics() == 1) {
            Graphic gg = graphic.getGraphicN(0);
            this.drawGraphic(gl, gg);
        } else {
            if (graphic instanceof SurfaceGraphics) {
                this.drawSurface(gl, (SurfaceGraphics) graphic);
            } else if (graphic instanceof IsosurfaceGraphics) {
                this.drawIsosurface(gl, (IsosurfaceGraphics) graphic);
            } else if (graphic instanceof ParticleGraphics) {
                this.drawParticles(gl, (ParticleGraphics) graphic);
            } else if (graphic instanceof VolumeGraphics) {
                try {
                    if (this.clipPlane)
                        this.disableClipPlane(gl);
                    //this.drawVolume(gl, (VolumeGraphics) graphic);
                    if (volumeRender == null)
                        volumeRender = new VolumeRender(gl, (VolumeGraphics) graphic);
                    else
                        volumeRender.updateShaders();
                    volumeRender.setTransform(this.transform);
                    volumeRender.setOrthographic(this.orthographic);
                    volumeRender.updateMatrix();
                    volumeRender.draw();
                    if (this.clipPlane)
                        this.enableClipPlane(gl);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                boolean isDraw = true;
                if (graphic instanceof GraphicCollection3D) {
                    GraphicCollection3D gg = (GraphicCollection3D) graphic;
                    if (gg.isAllQuads()) {
                        this.drawQuadsPolygons(gl, gg);
                        isDraw = false;
                    } else if (gg.isAllTriangle()) {
                        this.drawTrianglePolygons(gl, gg);
                        isDraw = false;
                    }
                }
                if (isDraw) {
                    switch (graphic.getGraphicN(0).getShape().getShapeType()) {
                        case POINT_Z:
                            if (((GraphicCollection3D) graphic).isSphere()) {
                                this.drawSpheres(gl, graphic);
                            } else {
                                this.drawPoints(gl, graphic);
                            }
                            break;
                        default:
                            for (int i = 0; i < graphic.getNumGraphics(); i++) {
                                Graphic gg = graphic.getGraphicN(i);
                                this.drawGraphic(gl, gg);
                            }
                            break;
                    }
                }
            }
        }
        if (graphic instanceof GraphicCollection3D) {
            if (lightEnabled && !((GraphicCollection3D)graphic).isUsingLight()) {
                this.lighting.start(gl);
            }
        }
    }

    private void drawGraphic(GL2 gl, Graphic graphic) {
        Shape shape = graphic.getGraphicN(0).getShape();
        switch (shape.getShapeType()) {
            case POINT:
            case POINT_Z:
                this.drawPoint(gl, graphic);
                break;
            case TEXT:
                //this.drawText(gl, (ChartText3D) shape);
                break;
            case POLYLINE:
            case POLYLINE_Z:
                ColorBreak cb = graphic.getLegend();
                if (cb instanceof StreamlineBreak) {
                    if (shape instanceof PipeShape) {
                        this.drawPipeStreamline(gl, graphic);
                    } else {
                        this.drawStreamline(gl, graphic);
                    }
                } else if (cb instanceof ColorBreakCollection) {
                    if (((ColorBreakCollection) cb).get(0) instanceof StreamlineBreak) {
                        if (shape instanceof PipeShape) {
                            this.drawPipeStreamline(gl, graphic);
                        } else {
                            this.drawStreamline(gl, graphic);
                        }
                    } else {
                        if (shape instanceof PipeShape) {
                            this.drawPipe(gl, graphic);
                        } else {
                            this.drawLineString(gl, graphic);
                        }
                    }
                } else {
                    if (shape instanceof PipeShape) {
                        this.drawPipe(gl, graphic);
                    } else {
                        this.drawLineString(gl, graphic);
                    }
                }
                break;
            case POLYGON:
            case POLYGON_Z:
                this.drawPolygonShape(gl, graphic);
                break;
            case WIND_ARROW:
                this.drawWindArrow(gl, graphic);
                break;
            case CUBIC:
                this.drawCubic(gl, graphic);
                break;
            case CYLINDER:
                this.drawCylinder(gl, graphic);
                break;
            case IMAGE:
                this.drawImage(gl, graphic);
                break;
            case TEXTURE:
                this.drawTexture(gl, graphic);
                break;
        }
    }

    protected void drawText(GL2 gl, ChartText3D text) {
        float[] xyz = this.transform.transform((float) text.getX(), (float) text.getY(), (float) text.getZ());
        float x = xyz[0];
        float y = xyz[1];
        float z = xyz[2];
        this.drawString(gl, text, x, y, z, text.getXAlign(), text.getYAlign());
    }

    private void drawPoint(GL2 gl, Graphic graphic) {
        boolean isDraw = true;
        if (this.clipPlane)
            isDraw = drawExtent.intersects(graphic.getExtent());

        if (isDraw) {
            PointZShape shape = (PointZShape) graphic.getShape();
            PointBreak pb = (PointBreak) graphic.getLegend();
            float[] rgba = pb.getColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glPointSize(pb.getSize() * this.dpiScale);
            gl.glBegin(GL2.GL_POINTS);
            PointZ p = (PointZ) shape.getPoint();
            gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
            gl.glEnd();
        }
    }

    private void drawPoints(GL2 gl, Graphic graphic) {
        PointBreak pb = (PointBreak) graphic.getGraphicN(0).getLegend();
        gl.glPointSize(pb.getSize() * this.dpiScale);
        gl.glBegin(GL2.GL_POINTS);
        for (Graphic gg : graphic.getGraphics()) {
            PointZShape shape = (PointZShape) gg.getShape();
            pb = (PointBreak) gg.getLegend();
            float[] rgba = pb.getColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            PointZ p = (PointZ) shape.getPoint();
            gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
        }
        gl.glEnd();
    }

    private void drawSphere(GL2 gl, Graphic graphic) {
        boolean isDraw = true;
        if (this.clipPlane)
            isDraw = drawExtent.intersects(graphic.getExtent());

        if (isDraw) {
            PointZShape shape = (PointZShape) graphic.getShape();
            PointBreak pb = (PointBreak) graphic.getLegend();
            float[] rgba = pb.getColor().getRGBComponents(null);
            gl.glColor4fv(rgba, 0);
            gl.glPushMatrix();
            PointZ p = (PointZ) shape.getPoint();
            float[] xyz = transform.transform((float) p.X, (float) p.Y, (float) p.Z);
            gl.glTranslated(xyz[0], xyz[1], xyz[2]);
            GLUquadric sphere = glu.gluNewQuadric();
            glu.gluQuadricDrawStyle(sphere, GLU.GLU_FILL);
            glu.gluQuadricNormals(sphere, GLU.GLU_FLAT);
            glu.gluQuadricOrientation(sphere, GLU.GLU_OUTSIDE);
            glu.gluSphere(sphere, pb.getSize() * 0.005 * this.dpiScale, 16, 16);
            glu.gluDeleteQuadric(sphere);
            gl.glPopMatrix();
        }
    }

    private void drawSpheres(GL2 gl, Graphic graphic) {
        for (Graphic gg : graphic.getGraphics()) {
            drawSphere(gl, gg);
        }
    }

    private void drawParticles(GL2 gl, ParticleGraphics particles) {
        for (Map.Entry map : particles.getParticleList()) {
            gl.glPointSize(particles.getPointSize() * this.dpiScale);
            gl.glBegin(GL2.GL_POINTS);
            for (ParticleGraphics.Particle p : (List)map.getValue()) {
                float[] rgba = p.rgba;
                gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                gl.glVertex3fv(transform.transform((float) p.x, (float) p.y, (float) p.z), 0);
            }
            gl.glEnd();
        }
    }

    private int getTextureID(GL2 gl) {
        IntBuffer intBuffer = IntBuffer.allocate(1);
        gl.glGenTextures(1, intBuffer);
        return intBuffer.get(0);
    }

    private void drawVolume(GL2 gl, VolumeGraphics volume) throws Exception {
        gl.glDisable(GL_DEPTH_TEST);

        gl.glActiveTexture(GL_TEXTURE1);
        gl.glBindTexture(GL_TEXTURE_2D, getTextureID(gl));
        gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, Buffers.newDirectByteBuffer(volume.getColors()));

        int idData = getTextureID(gl);
        gl.glActiveTexture(GL_TEXTURE0);
        gl.glBindTexture(GL_TEXTURE_3D, idData);
        gl.glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 0);
        gl.glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        gl.glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        gl.glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        gl.glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        gl.glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

        String vertexShaderCode = Utils.loadResource("/shaders/volume/vertex.vert");
        String fragmentShaderCode = Utils.loadResource("/shaders/volume/maxValue.frag");
        final Program program = new Program("volume", vertexShaderCode, fragmentShaderCode);
        try {
            program.init(gl);
        } catch (Exception e) {
            e.printStackTrace();
        }

        IntBuffer intBuffer = IntBuffer.allocate(1);
        gl.glGenBuffers(1, intBuffer);
        int vertexBuffer = intBuffer.get(0);
        gl.glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        float[] vertexBufferData = volume.getVertexBufferData(this.transform);
        gl.glBufferData(GL_ARRAY_BUFFER, vertexBufferData.length * Float.BYTES, Buffers.newDirectFloatBuffer(vertexBufferData), GL_STATIC_DRAW);
        program.allocateUniform(gl, "orthographic", (gl2, loc) -> {
            gl2.glUniform1i(loc, this.orthographic ? 1 : 0);
        });
        program.allocateUniform(gl, "MVP", (gl2, loc) -> {
            //gl2.glUniformMatrix4fv(loc, 1, false, this.camera.getViewProjectionMatrix().get(Buffers.newDirectFloatBuffer(16)));
            gl2.glUniformMatrix4fv(loc, 1, false, this.viewProjMatrix.get(Buffers.newDirectFloatBuffer(16)));
        });
        program.allocateUniform(gl, "iV", (gl2, loc) -> {
            //gl2.glUniformMatrix4fv(loc, 1, false, this.camera.getViewMatrix().invert().get(Buffers.newDirectFloatBuffer(16)));
            gl2.glUniformMatrix4fv(loc, 1, false, toMatrix(this.mvmatrix).invert().get(Buffers.newDirectFloatBuffer(16)));
        });
        program.allocateUniform(gl, "iP", (gl2, loc) -> {
            //gl2.glUniformMatrix4fv(loc, 1, false, this.camera.getProjectionMatrix().invert().get(Buffers.newDirectFloatBuffer(16)));
            gl2.glUniformMatrix4fv(loc, 1, false, toMatrix(this.projmatrix).invert().get(Buffers.newDirectFloatBuffer(16)));
        });
        program.allocateUniform(gl, "viewSize", (gl2, loc) -> {
            gl2.glUniform2f(loc, this.width, this.height);
        });
        int sampleCount = 512;
        program.allocateUniform(gl, "depthSampleCount", (gl2, loc) -> {
            gl2.glUniform1i(loc, sampleCount);
        });
        program.allocateUniform(gl, "tex", (gl2, loc) -> {
            gl2.glUniform1i(loc, 0);
        });
        program.allocateUniform(gl, "colorMap", (gl2, loc) -> {
            gl2.glUniform1i(loc, 1);
        });
        float[] aabbMin = volume.getAabbMin();
        float[] aabbMax = volume.getAabbMax();
        program.allocateUniform(gl, "aabbMin", (gl2, loc) -> {
            gl2.glUniform3f(loc, aabbMin[0], aabbMin[1], aabbMin[2]);
        });
        program.allocateUniform(gl, "aabbMax", (gl2, loc) -> {
            gl2.glUniform3f(loc, aabbMax[0], aabbMax[1], aabbMax[2]);
        });

        program.use(gl);
        program.setUniforms(gl);

        gl.glActiveTexture(GL_TEXTURE1);
        gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, volume.getColorNum(), 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                Buffers.newDirectByteBuffer(volume.getColors()).rewind());

        gl.glActiveTexture(GL_TEXTURE0);
        gl.glBindTexture(GL_TEXTURE_3D, idData);
        gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        gl.glTexImage3D(
                GL_TEXTURE_3D,  // target
                0,              // level
                GL_LUMINANCE,        // internal format
                volume.getWidth(),           // width
                volume.getHeight(),           // height
                volume.getDepth(),           // depth
                0,              // border
                GL_LUMINANCE,         // format
                GL_UNSIGNED_BYTE,       // type
                Buffers.newDirectByteBuffer(volume.getData()).rewind()           // pixel
        );

        // 1st attribute buffer : vertices
        gl.glEnableVertexAttribArray(0);
        gl.glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        gl.glVertexAttribPointer(
                0,                  // attribute 0. No particular reason for 0, but must match the layout in the shader.
                3,                  // size
                GL_FLOAT,           // type
                false,           // normalized?
                3 * 4,                  // stride
                0            // array buffer offset
        );

        // Draw the triangle !
        gl.glDrawArrays(GL_TRIANGLES, 0, vertexBufferData.length / 3); // Starting from vertex 0; 3 vertices total -> 1 triangle
        gl.glDisableVertexAttribArray(0);

        Program.destroyAllPrograms(gl);
        gl.glUseProgram(0);
        gl.glEnable(GL_DEPTH_TEST);
    }

    private void drawLineString(GL2 gl, Graphic graphic) {
        boolean isDraw = true;
        if (this.clipPlane)
            isDraw = drawExtent.intersects(graphic.getExtent());

        if (isDraw) {
            PolylineZShape shape = (PolylineZShape) graphic.getShape();
            ColorBreak cb = graphic.getLegend();
            if (cb.getBreakType() == BreakTypes.COLOR_BREAK_COLLECTION) {
                ColorBreakCollection cbc = (ColorBreakCollection) cb;
                Polyline line = shape.getPolylines().get(0);
                List ps = (List) line.getPointList();
                float[] rgba;
                PointZ p;
                gl.glLineWidth(((PolylineBreak) cbc.get(0)).getWidth() * this.dpiScale);
                gl.glBegin(GL2.GL_LINE_STRIP);
                for (int i = 0; i < ps.size(); i++) {
                    PolylineBreak plb = (PolylineBreak) cbc.get(i);
                    rgba = plb.getColor().getRGBComponents(null);
                    gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                    gl.glLineWidth(plb.getWidth() * this.dpiScale);
                    p = ps.get(i);
                    gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
                }
                gl.glEnd();
            } else {
                PolylineBreak pb = (PolylineBreak) cb;
                float[] rgba = pb.getColor().getRGBComponents(null);
                gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                gl.glLineWidth(pb.getWidth() * this.dpiScale);
                for (Polyline line : shape.getPolylines()) {
                    gl.glBegin(GL2.GL_LINE_STRIP);
                    List ps = (List) line.getPointList();
                    for (PointZ p : ps) {
                        gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
                    }
                    gl.glEnd();
                }
            }
        }
    }

    private void drawPipe(GL2 gl, Graphic graphic) {
        boolean isDraw = true;
        if (this.clipPlane)
            isDraw = drawExtent.intersects(graphic.getExtent());

        if (isDraw) {
            PipeShape shape = (PipeShape) graphic.getShape();
            ColorBreak cb = graphic.getLegend();
            shape.transform(transform);
            Pipe pipe = shape.getPipe();
            int count = pipe.getContourCount();
            if (cb.getBreakType() == BreakTypes.COLOR_BREAK_COLLECTION) {
                ColorBreakCollection cbc = (ColorBreakCollection) cb;
                float[] rgba;
                for (int i = 0; i < count - 1; i++) {
                    PolylineBreak plb = (PolylineBreak) cbc.get(i);
                    rgba = plb.getColor().getRGBComponents(null);
                    gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                    Vector c1 = pipe.getContour(i);
                    Vector c2 = pipe.getContour(i+1);
                    Vector n1 = pipe.getNormal(i);
                    Vector n2 = pipe.getNormal(i+1);
                    gl.glBegin(GL_TRIANGLE_STRIP);
                    for(int j = 0; j < (int)c2.size(); ++j)
                    {
                        gl.glNormal3fv(JOGLUtil.toArray(n2.get(j)), 0);
                        gl.glVertex3fv(JOGLUtil.toArray(c2.get(j)), 0);
                        gl.glNormal3fv(JOGLUtil.toArray(n1.get(j)), 0);
                        gl.glVertex3fv(JOGLUtil.toArray(c1.get(j)), 0);
                    }
                    gl.glEnd();
                }
                gl.glEnd();
            } else {
                PolylineBreak pb = (PolylineBreak) cb;
                float[] rgba = pb.getColor().getRGBComponents(null);
                gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                for(int i = 0; i < count - 1; i++)
                {
                    Vector c1 = pipe.getContour(i);
                    Vector c2 = pipe.getContour(i+1);
                    Vector n1 = pipe.getNormal(i);
                    Vector n2 = pipe.getNormal(i+1);
                    gl.glBegin(GL_TRIANGLE_STRIP);
                    for(int j = 0; j < (int)c2.size(); ++j)
                    {
                        gl.glNormal3fv(JOGLUtil.toArray(n2.get(j)), 0);
                        gl.glVertex3fv(JOGLUtil.toArray(c2.get(j)), 0);
                        gl.glNormal3fv(JOGLUtil.toArray(n1.get(j)), 0);
                        gl.glVertex3fv(JOGLUtil.toArray(c1.get(j)), 0);
                    }
                    gl.glEnd();
                }
            }
        }
    }

    private void drawStreamline(GL2 gl, Graphic graphic) {
        boolean isDraw = true;
        if (this.clipPlane)
            isDraw = drawExtent.intersects(graphic.getExtent());

        if (isDraw) {
            PolylineZShape shape = (PolylineZShape) graphic.getShape();
            ColorBreak cb = graphic.getLegend();
            if (cb.getBreakType() == BreakTypes.COLOR_BREAK_COLLECTION) {
                ColorBreakCollection cbc = (ColorBreakCollection) cb;
                Polyline line = shape.getPolylines().get(0);
                List ps = (List) line.getPointList();
                float[] rgba;
                PointZ p;
                gl.glLineWidth(((PolylineBreak) cbc.get(0)).getWidth() * this.dpiScale);
                gl.glBegin(GL2.GL_LINE_STRIP);
                for (int i = 0; i < ps.size(); i++) {
                    PolylineBreak plb = (PolylineBreak) cbc.get(i);
                    rgba = plb.getColor().getRGBComponents(null);
                    gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                    gl.glLineWidth(plb.getWidth() * this.dpiScale);
                    p = ps.get(i);
                    gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
                }
                gl.glEnd();

                //Draw arrow
                StreamlineBreak slb = (StreamlineBreak) cbc.get(0);
                int interval = slb.getInterval();
                if (slb.getArrowHeadLength() > 0 || slb.getArrowHeadWidth() > 0) {
                    float[] p2, p1;
                    PointZ pp;
                    for (int i = 0; i < ps.size(); i++) {
                        slb = (StreamlineBreak) cbc.get(i);
                        pp = ps.get(i);
                        p2 = transform.transform((float) pp.X, (float) pp.Y, (float) pp.Z);
                        if (i > 0 && i % interval == 0) {
                            pp = ps.get(i - 1);
                            p1 = transform.transform((float) pp.X, (float) pp.Y, (float) pp.Z);
                            drawArrow(gl, p2, p1, slb);
                        }
                    }
                }
            } else {
                StreamlineBreak slb = (StreamlineBreak) cb;
                int interval = slb.getInterval() * 3;
                float[] rgba = slb.getColor().getRGBComponents(null);
                gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                gl.glLineWidth(slb.getWidth() * this.dpiScale);
                gl.glBegin(GL2.GL_LINE_STRIP);
                for (Polyline line : shape.getPolylines()) {
                    List ps = (List) line.getPointList();
                    float[] p;
                    PointZ pp;
                    for (int i = 0; i < ps.size(); i++) {
                        pp = ps.get(i);
                        p = transform.transform((float) pp.X, (float) pp.Y, (float) pp.Z);
                        gl.glVertex3f(p[0], p[1], p[2]);
                    }
                }
                gl.glEnd();

                //Draw arrow
                if (slb.getArrowHeadLength() > 0 || slb.getArrowHeadWidth() > 0) {
                    for (Polyline line : shape.getPolylines()) {
                        List ps = (List) line.getPointList();
                        float[] p, p1;
                        PointZ pp;
                        for (int i = 0; i < ps.size(); i++) {
                            pp = ps.get(i);
                            p = transform.transform((float) pp.X, (float) pp.Y, (float) pp.Z);
                            if (i > 0 && i % interval == 0) {
                                pp = ps.get(i - 1);
                                p1 = transform.transform((float) pp.X, (float) pp.Y, (float) pp.Z);
                                drawArrow(gl, p, p1, slb);
                            }
                        }
                    }
                }
            }
        }
    }

    private void drawPipeStreamline(GL2 gl, Graphic graphic) {
        boolean isDraw = true;
        if (this.clipPlane)
            isDraw = drawExtent.intersects(graphic.getExtent());

        if (isDraw) {
            PipeShape shape = (PipeShape) graphic.getShape();
            ColorBreak cb = graphic.getLegend();
            shape.transform(transform);
            Pipe pipe = shape.getPipe();
            int count = pipe.getContourCount();
            if (cb.getBreakType() == BreakTypes.COLOR_BREAK_COLLECTION) {
                ColorBreakCollection cbc = (ColorBreakCollection) cb;
                float[] rgba;
                for (int i = 0; i < count - 1; i++) {
                    StreamlineBreak plb = (StreamlineBreak) cbc.get(i);
                    rgba = plb.getColor().getRGBComponents(null);
                    gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                    Vector c1 = pipe.getContour(i);
                    Vector c2 = pipe.getContour(i+1);
                    Vector n1 = pipe.getNormal(i);
                    Vector n2 = pipe.getNormal(i+1);
                    gl.glBegin(GL_TRIANGLE_STRIP);
                    for(int j = 0; j < (int)c2.size(); ++j)
                    {
                        gl.glNormal3fv(JOGLUtil.toArray(n2.get(j)), 0);
                        gl.glVertex3fv(JOGLUtil.toArray(c2.get(j)), 0);
                        gl.glNormal3fv(JOGLUtil.toArray(n1.get(j)), 0);
                        gl.glVertex3fv(JOGLUtil.toArray(c1.get(j)), 0);
                    }
                    gl.glEnd();
                }

                //Draw arrow
                Vector path = pipe.getPath();
                StreamlineBreak slb = (StreamlineBreak) cbc.get(0);
                int interval = slb.getInterval();
                if (slb.getArrowHeadLength() > 0 || slb.getArrowHeadWidth() > 0) {
                    float[] p2, p1;
                    Vector3f pp;
                    for (int i = 0; i < path.size(); i++) {
                        slb = (StreamlineBreak) cbc.get(i);
                        pp = path.get(i);
                        p2 = new float[]{pp.x, pp.y, pp.z};
                        if (i > 0 && i % interval == 0) {
                            pp = path.get(i - 1);
                            p1 = new float[]{pp.x, pp.y, pp.z};
                            drawArrow(gl, p2, p1, slb);
                        }
                    }
                }
            } else {
                StreamlineBreak slb = (StreamlineBreak) cb;
                int interval = slb.getInterval() * 3;
                float[] rgba = slb.getColor().getRGBComponents(null);
                gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                for(int i = 0; i < count - 1; i++)
                {
                    Vector c1 = pipe.getContour(i);
                    Vector c2 = pipe.getContour(i+1);
                    Vector n1 = pipe.getNormal(i);
                    Vector n2 = pipe.getNormal(i+1);
                    gl.glBegin(GL_TRIANGLE_STRIP);
                    for(int j = 0; j < (int)c2.size(); ++j)
                    {
                        gl.glNormal3fv(JOGLUtil.toArray(n2.get(j)), 0);
                        gl.glVertex3fv(JOGLUtil.toArray(c2.get(j)), 0);
                        gl.glNormal3fv(JOGLUtil.toArray(n1.get(j)), 0);
                        gl.glVertex3fv(JOGLUtil.toArray(c1.get(j)), 0);
                    }
                    gl.glEnd();
                }

                //Draw arrow
                Vector path = pipe.getPath();
                if (slb.getArrowHeadLength() > 0 || slb.getArrowHeadWidth() > 0) {
                    float[] p2, p1;
                    Vector3f pp;
                    for (int i = 0; i < path.size(); i++) {
                        pp = path.get(i);
                        p2 = new float[]{pp.x, pp.y, pp.z};
                        if (i > 0 && i % interval == 0) {
                            pp = path.get(i - 1);
                            p1 = new float[]{pp.x, pp.y, pp.z};
                            drawArrow(gl, p2, p1, slb);
                        }
                    }
                }
            }
        }
    }

    private void drawPolygonShape(GL2 gl, Graphic graphic) {
        boolean isDraw = true;
        if (this.clipPlane)
            isDraw = drawExtent.intersects(graphic.getExtent());

        if (isDraw) {
            PolygonZShape shape = (PolygonZShape) graphic.getShape();
            PolygonBreak pb = (PolygonBreak) graphic.getLegend();
            List polygonZS = (List) shape.getPolygons();
            for (int i = 0; i < polygonZS.size(); i++) {
                PolygonZ polygonZ = polygonZS.get(i);
                if (pb.isDrawFill()) {
                    if (polygonZ instanceof TessPolygon) {
                        drawTessPolygon(gl, (TessPolygon) polygonZ, pb);
                    } else {
                        if (polygonZ.getOutLine().size() <= 5) {
                            drawConvexPolygon(gl, polygonZ, pb);
                        } else {
                            TessPolygon tessPolygon = new TessPolygon(polygonZ);
                            drawTessPolygon(gl, tessPolygon, pb);
                            polygonZS.set(i, tessPolygon);
                        }
                    }
                } else {
                    drawPolygon(gl, polygonZ, pb);
                }
             }
        }
    }

    private void drawTessPolygon(GL2 gl, TessPolygon tessPolygon, PolygonBreak aPGB) {
        if (aPGB.isDrawFill() && aPGB.getColor().getAlpha() > 0) {
            gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL);
            gl.glPolygonOffset(1.0f, 1.0f);

            float[] rgba = aPGB.getColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);

            try {
                for (Primitive primitive : tessPolygon.getPrimitives()) {
                    gl.glBegin(primitive.type);
                    for (PointZ p : primitive.vertices) {
                        gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
                    }
                    gl.glEnd();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (aPGB.isDrawOutline()) {
            float[] rgba = aPGB.getOutlineColor().getRGBComponents(null);
            gl.glLineWidth(aPGB.getOutlineSize() * this.dpiScale);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_LINE_STRIP);
            PointZ p;
            for (int i = 0; i < tessPolygon.getOutLine().size(); i++) {
                p = ((List) tessPolygon.getOutLine()).get(i);
                gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
            }
            gl.glEnd();

            if (tessPolygon.hasHole()) {
                List newPList;
                for (int h = 0; h < tessPolygon.getHoleLines().size(); h++) {
                    gl.glBegin(GL2.GL_LINE_STRIP);
                    newPList = (List) tessPolygon.getHoleLines().get(h);
                    for (int j = 0; j < newPList.size(); j++) {
                        p = newPList.get(j);
                        gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
                    }
                    gl.glEnd();
                }
            }
            gl.glDisable(GL2.GL_POLYGON_OFFSET_FILL);
        }
    }

    private void drawPolygon(GL2 gl, PolygonZ aPG, PolygonBreak aPGB) {
        if (aPGB.isDrawFill() && aPGB.getColor().getAlpha() > 0) {
            gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL);
            gl.glPolygonOffset(1.0f, 1.0f);

            float[] rgba = aPGB.getColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);

            try {
                TessPolygon tessPolygon = new TessPolygon(aPG);
                for (Primitive primitive : tessPolygon.getPrimitives()) {
                    gl.glBegin(primitive.type);
                    for (PointZ p : primitive.vertices) {
                        gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
                    }
                    gl.glEnd();
                }
                aPG = tessPolygon;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (aPGB.isDrawOutline()) {
            float[] rgba = aPGB.getOutlineColor().getRGBComponents(null);
            gl.glLineWidth(aPGB.getOutlineSize() * this.dpiScale);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_LINE_STRIP);
            PointZ p;
            for (int i = 0; i < aPG.getOutLine().size(); i++) {
                p = ((List) aPG.getOutLine()).get(i);
                gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
            }
            gl.glEnd();

            if (aPG.hasHole()) {
                List newPList;
                gl.glBegin(GL2.GL_LINE_STRIP);
                for (int h = 0; h < aPG.getHoleLines().size(); h++) {
                    newPList = (List) aPG.getHoleLines().get(h);
                    for (int j = 0; j < newPList.size(); j++) {
                        p = newPList.get(j);
                        gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
                    }
                }
                gl.glEnd();
            }
            gl.glDisable(GL2.GL_POLYGON_OFFSET_FILL);
        }
    }

    private void drawPolygon_bak(GL2 gl, PolygonZ aPG, PolygonBreak aPGB) {
        PointZ p;
        if (aPGB.isDrawFill() && aPGB.getColor().getAlpha() > 0) {
            gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL);
            gl.glPolygonOffset(1.0f, 1.0f);

            float[] rgba = aPGB.getColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);

            try {
                GLUtessellator tobj = glu.gluNewTess();
                //TessCallback tessCallback = new TessCallback(gl, glu);

                glu.gluTessCallback(tobj, GLU.GLU_TESS_VERTEX, tessCallback);
                glu.gluTessCallback(tobj, GLU.GLU_TESS_BEGIN, tessCallback);
                glu.gluTessCallback(tobj, GLU.GLU_TESS_END, tessCallback);
                glu.gluTessCallback(tobj, GLU.GLU_TESS_ERROR, tessCallback);
                //glu.gluTessCallback(tobj, GLU.GLU_TESS_COMBINE, tessCallback);

                //gl.glNewList(startList, GL2.GL_COMPILE);
                //gl.glShadeModel(GL2.GL_FLAT);
                glu.gluTessBeginPolygon(tobj, null);
                glu.gluTessBeginContour(tobj);
                double[] v;
                for (int i = 0; i < aPG.getOutLine().size() - 1; i++) {
                    p = ((List) aPG.getOutLine()).get(i);
                    v = transform.transform(p);
                    glu.gluTessVertex(tobj, v, 0, v);
                }
                glu.gluTessEndContour(tobj);
                if (aPG.hasHole()) {
                    for (int i = 0; i < aPG.getHoleLineNumber(); i++) {
                        glu.gluTessBeginContour(tobj);
                        for (int j = 0; j < aPG.getHoleLine(i).size() - 1; j++) {
                            p = ((List) aPG.getHoleLine(i)).get(j);
                            v = transform.transform(p);
                            glu.gluTessVertex(tobj, v, 0, v);
                        }
                        glu.gluTessEndContour(tobj);
                    }
                }
                glu.gluTessEndPolygon(tobj);
                //gl.glEndList();
                glu.gluDeleteTess(tobj);

                //gl.glCallList(startList);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (aPGB.isDrawOutline()) {
            float[] rgba = aPGB.getOutlineColor().getRGBComponents(null);
            gl.glLineWidth(aPGB.getOutlineSize() * this.dpiScale);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_LINE_STRIP);
            for (int i = 0; i < aPG.getOutLine().size(); i++) {
                p = ((List) aPG.getOutLine()).get(i);
                gl.glVertex3f(transform.transform_x((float) p.X), transform.transform_y((float) p.Y), transform.transform_z((float) p.Z));
            }
            gl.glEnd();

            if (aPG.hasHole()) {
                List newPList;
                gl.glBegin(GL2.GL_LINE_STRIP);
                for (int h = 0; h < aPG.getHoleLines().size(); h++) {
                    newPList = (List) aPG.getHoleLines().get(h);
                    for (int j = 0; j < newPList.size(); j++) {
                        p = newPList.get(j);
                        gl.glVertex3f(transform.transform_x((float) p.X), transform.transform_y((float) p.Y), transform.transform_z((float) p.Z));
                    }
                }
                gl.glEnd();
            }
            gl.glDisable(GL2.GL_POLYGON_OFFSET_FILL);
        }
    }

    private void drawConvexPolygon(GL2 gl, PolygonZ aPG, PolygonBreak aPGB) {
        PointZ p;
        if (aPGB.isDrawFill()) {
            float[] rgba = aPGB.getColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_POLYGON);
            for (int i = 0; i < aPG.getOutLine().size(); i++) {
                p = ((List) aPG.getOutLine()).get(i);
                gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
            }
            gl.glEnd();
        }

        if (aPGB.isDrawOutline()) {
            float[] rgba = aPGB.getOutlineColor().getRGBComponents(null);
            gl.glLineWidth(aPGB.getOutlineSize() * this.dpiScale);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_LINE_STRIP);
            for (int i = 0; i < aPG.getOutLine().size(); i++) {
                p = ((List) aPG.getOutLine()).get(i);
                gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
            }
            gl.glEnd();
        }
    }

    private void drawQuadsPolygons(GL2 gl, GraphicCollection3D graphic) {
        PointZ p;
        for (int i = 0; i < graphic.getNumGraphics(); i++) {
            Graphic gg = graphic.getGraphicN(i);
            boolean isDraw = true;
            if (this.clipPlane)
                isDraw = drawExtent.intersects(gg.getExtent());

            if (isDraw) {
                PolygonZShape shape = (PolygonZShape) gg.getShape();
                PolygonBreak pb = (PolygonBreak) gg.getLegend();
                for (PolygonZ poly : (List) shape.getPolygons()) {
                    drawQuads(gl, poly, pb);
                }
            }
        }
    }

    private void drawQuads(GL2 gl, PolygonZ aPG, PolygonBreak aPGB) {
        PointZ p;
        float[] rgba = aPGB.getColor().getRGBComponents(null);
        if (aPGB.isDrawFill()) {
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_QUADS);
            for (int i = 0; i < aPG.getOutLine().size(); i++) {
                p = ((List) aPG.getOutLine()).get(i);
                gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
            }
            gl.glEnd();
        }

        if (aPGB.isDrawOutline()) {
            rgba = aPGB.getOutlineColor().getRGBComponents(null);
            gl.glLineWidth(aPGB.getOutlineSize() * this.dpiScale);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_LINE_STRIP);
            for (int i = 0; i < aPG.getOutLine().size(); i++) {
                p = ((List) aPG.getOutLine()).get(i);
                gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
            }
            gl.glEnd();
        }
    }

    private void drawTrianglePolygons(GL2 gl, GraphicCollection3D graphic) {
        PointZ p;
        for (int i = 0; i < graphic.getNumGraphics(); i++) {
            Graphic gg = graphic.getGraphicN(i);
            boolean isDraw = true;
            if (this.clipPlane)
                isDraw = drawExtent.intersects(gg.getExtent());

            if (isDraw) {
                PolygonZShape shape = (PolygonZShape) gg.getShape();
                PolygonBreak pb = (PolygonBreak) gg.getLegend();
                for (PolygonZ poly : (List) shape.getPolygons()) {
                    drawTriangle(gl, poly, pb);
                }
            }
        }
    }

    private void drawTriangle(GL2 gl, PolygonZ aPG, PolygonBreak aPGB) {
        PointZ p;
        float[] rgba = aPGB.getColor().getRGBComponents(null);
        if (aPGB.isDrawFill()) {
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_TRIANGLES);
            for (int i = 0; i < aPG.getOutLine().size(); i++) {
                p = ((List) aPG.getOutLine()).get(i);
                gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
            }
            gl.glEnd();
        }

        if (aPGB.isDrawOutline()) {
            rgba = aPGB.getOutlineColor().getRGBComponents(null);
            gl.glLineWidth(aPGB.getOutlineSize() * this.dpiScale);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_LINE_STRIP);
            for (int i = 0; i < aPG.getOutLine().size(); i++) {
                p = ((List) aPG.getOutLine()).get(i);
                gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
            }
            gl.glEnd();
        }
    }

    private void drawTriangle(GL2 gl, PointZ[] points, PolygonBreak aPGB) {
        PointZ p;
        float[] rgba = aPGB.getColor().getRGBComponents(null);
        if (aPGB.isDrawFill()) {
            gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL);
            gl.glPolygonOffset(1.0f, 1.0f);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            float[] x0 = transform.transformf(points[0]);
            float[] x1 = transform.transformf(points[1]);
            float[] x2 = transform.transformf(points[2]);
            gl.glBegin(GL2.GL_TRIANGLES);
            if (this.lighting.isEnable()) {
                float[] normal = JOGLUtil.normalize(x0, x1, x2);
                gl.glNormal3fv(normal, 0);
            }
            gl.glVertex3fv(x0, 0);
            gl.glVertex3fv(x1, 0);
            gl.glVertex3fv(x2, 0);
            gl.glEnd();
        }

        if (aPGB.isDrawOutline()) {
            rgba = aPGB.getOutlineColor().getRGBComponents(null);
            gl.glLineWidth(aPGB.getOutlineSize() * this.dpiScale);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_LINE_STRIP);
            for (int i = 0; i < 3; i++) {
                p = points[i];
                gl.glVertex3fv(transform.transform((float) p.X, (float) p.Y, (float) p.Z), 0);
            }
            gl.glEnd();
        }
    }

    private void drawSurface(GL2 gl, SurfaceGraphics surface) {
        PolygonBreak pgb = (PolygonBreak) surface.getLegendBreak(0, 0);
        int dim1 = surface.getDim1();
        int dim2 = surface.getDim2();
        float[] rgba;
        Vector3f p;
        boolean lightEnabled = this.lighting.isEnable();
        boolean usingLight = lightEnabled && surface.isUsingLight();
        if (lightEnabled && !surface.isUsingLight()) {
            this.lighting.stop(gl);
        }

        surface.transform(transform);
        if (pgb.isDrawOutline()) {
            gl.glLineWidth(pgb.getOutlineSize() * this.dpiScale);
            if (surface.isEdgeInterp()) {
                for (int i = 0; i < dim1; i++) {
                    gl.glBegin(GL2.GL_LINE_STRIP);
                    for (int j = 0; j < dim2; j++) {
                        rgba = surface.getEdgeRGBA(i, j);
                        gl.glColor4fv(rgba, 0);
                        gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i, j)), 0);
                    }
                    gl.glEnd();
                }
                for (int j = 0; j < dim2; j++) {
                    gl.glBegin(GL2.GL_LINE_STRIP);
                    for (int i = 0; i < dim1; i++) {
                        rgba = surface.getEdgeRGBA(i, j);
                        gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                        gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i, j)), 0);
                    }
                    gl.glEnd();
                }
            } else {
                float[] vertex;
                for (int i = 0; i < dim1; i++) {
                    p = surface.getTVertex(i, 0);
                    vertex = JOGLUtil.toArray(p);
                    for (int j = 0; j < dim2 - 1; j++) {
                        rgba = surface.getEdgeRGBA(i, j);
                        gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                        gl.glBegin(GL2.GL_LINES);
                        gl.glVertex3fv(vertex, 0);
                        p = surface.getTVertex(i, j + 1);
                        vertex = JOGLUtil.toArray(p);
                        gl.glVertex3fv(vertex, 0);
                        gl.glEnd();
                    }
                }
                for (int j = 0; j < dim2; j++) {
                    p = surface.getTVertex(0, j);
                    vertex = JOGLUtil.toArray(p);
                    for (int i = 0; i < dim1 - 1; i++) {
                        p = surface.getTVertex(i, j);
                        rgba = surface.getEdgeRGBA(i, j);
                        gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                        gl.glBegin(GL2.GL_LINES);
                        gl.glVertex3fv(vertex, 0);
                        p = surface.getTVertex(i + 1, j);
                        vertex = JOGLUtil.toArray(p);
                        gl.glVertex3fv(vertex, 0);
                        gl.glEnd();
                    }
                }
            }
        }

        BufferedImage image = surface.getImage();
        if (image == null) {
            if (pgb.isDrawFill()) {
                gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL);
                gl.glPolygonOffset(1.0f, 1.0f);
                if (surface.isFaceInterp()) {
                    for (int i = 0; i < dim1 - 1; i++) {
                        for (int j = 0; j < dim2 - 1; j++) {
                            gl.glBegin(GL2.GL_QUADS);
                            rgba = surface.getRGBA(i, j);
                            gl.glColor4fv(rgba, 0);
                            gl.glNormal3fv(JOGLUtil.toArray(surface.getNormal(i, j)), 0);
                            gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i, j)), 0);
                            rgba = surface.getRGBA(i + 1, j);
                            gl.glColor4fv(rgba, 0);
                            gl.glNormal3fv(JOGLUtil.toArray(surface.getNormal(i + 1, j)), 0);
                            gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i + 1, j)), 0);
                            rgba = surface.getRGBA(i + 1, j + 1);
                            gl.glColor4fv(rgba, 0);
                            gl.glNormal3fv(JOGLUtil.toArray(surface.getNormal(i + 1, j + 1)), 0);
                            gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i + 1, j + 1)), 0);
                            rgba = surface.getRGBA(i, j + 1);
                            gl.glColor4fv(rgba, 0);
                            gl.glNormal3fv(JOGLUtil.toArray(surface.getNormal(i, j + 1)), 0);
                            gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i, j + 1)), 0);
                            gl.glEnd();
                        }
                    }
                } else {
                    for (int i = 0; i < dim1 - 1; i++) {
                        for (int j = 0; j < dim2 - 1; j++) {
                            gl.glBegin(GL2.GL_QUADS);
                            rgba = surface.getRGBA(i, j);
                            gl.glColor4fv(rgba, 0);
                            gl.glNormal3fv(JOGLUtil.toArray(surface.getNormal(i, j)), 0);
                            gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i, j)), 0);
                            gl.glNormal3fv(JOGLUtil.toArray(surface.getNormal(i + 1, j)), 0);
                            gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i + 1, j)), 0);
                            gl.glNormal3fv(JOGLUtil.toArray(surface.getNormal(i + 1, j + 1)), 0);
                            gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i + 1, j + 1)), 0);
                            gl.glNormal3fv(JOGLUtil.toArray(surface.getNormal(i, j + 1)), 0);
                            gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i, j + 1)), 0);
                            gl.glEnd();
                        }
                    }
                }
                gl.glDisable(GL2.GL_POLYGON_OFFSET_FILL);
            }
        } else {
            surface.updateTexture(gl);
            int idTexture = surface.getTextureID();

            gl.glEnable(GL2.GL_TEXTURE_2D);
            gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL);
            gl.glPolygonOffset(1.0f, 1.0f);

            gl.glColor3f(1f, 1f, 1f);
            gl.glBindTexture(GL2.GL_TEXTURE_2D, idTexture);

            // Texture parameterization
            gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
            gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);

            // Draw image
            gl.glBegin(GL2.GL_QUADS);
            // Front Face
            float float_x, float_y, float_xb, float_yb;
            for (int i = 0; i < dim1 - 1; i++) {
                for (int j = 0; j < dim2 - 1; j++) {
                    float_y = (float) (i) / (dim1 - 1);
                    //float_y = 1.0f - (float) (i) / dim1;
                    float_x = (float) (j) / (dim2 - 1);
                    float_yb = (float) (i + 1) / (dim1 - 1);
                    //float_yb = 1.0f - (float) (i + 1) / dim1;
                    float_xb = (float) (j + 1) / (dim2 - 1);
                    gl.glNormal3fv(JOGLUtil.toArray(surface.getNormal(i, j)), 0);
                    gl.glTexCoord2f(float_x, float_y);
                    gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i, j)), 0);

                    gl.glNormal3fv(JOGLUtil.toArray(surface.getNormal(i + 1, j)), 0);
                    gl.glTexCoord2f(float_x, float_yb);
                    gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i + 1, j)), 0);

                    gl.glNormal3fv(JOGLUtil.toArray(surface.getNormal(i + 1, j + 1)), 0);
                    gl.glTexCoord2f(float_xb, float_yb);
                    gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i + 1, j + 1)), 0);

                    gl.glNormal3fv(JOGLUtil.toArray(surface.getNormal(i, j + 1)), 0);
                    gl.glTexCoord2f(float_xb, float_y);
                    gl.glVertex3fv(JOGLUtil.toArray(surface.getTVertex(i, j + 1)), 0);
                }
            }
            gl.glEnd();

            // Unbinding the texture
            gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);

            gl.glDisable(GL2.GL_POLYGON_OFFSET_FILL);
            gl.glDisable(GL2.GL_TEXTURE_2D);
        }

        if (lightEnabled && !surface.isUsingLight()) {
            this.lighting.start(gl);
        }
    }

    private void drawSurface_bak(GL2 gl, SurfaceGraphics surface) {
        PolygonBreak pgb = (PolygonBreak) surface.getLegendBreak(0, 0);
        int dim1 = surface.getDim1();
        int dim2 = surface.getDim2();
        float[] rgba;
        PointZ p;
        boolean lightEnabled = this.lighting.isEnable();
        boolean usingLight = lightEnabled && surface.isUsingLight();
        if (lightEnabled && !surface.isUsingLight()) {
            this.lighting.stop(gl);
        }

        if (pgb.isDrawOutline()) {
            gl.glLineWidth(pgb.getOutlineSize() * this.dpiScale);
            if (surface.isEdgeInterp()) {
                for (int i = 0; i < dim1; i++) {
                    gl.glBegin(GL2.GL_LINE_STRIP);
                    for (int j = 0; j < dim2; j++) {
                        p = surface.getVertex(i, j);
                        rgba = surface.getEdgeRGBA(i, j);
                        gl.glColor4fv(rgba, 0);
                        gl.glVertex3f(transform.transform_x((float) p.X), transform.transform_y((float) p.Y), transform.transform_z((float) p.Z));
                    }
                    gl.glEnd();
                }
                for (int j = 0; j < dim2; j++) {
                    gl.glBegin(GL2.GL_LINE_STRIP);
                    for (int i = 0; i < dim1; i++) {
                        p = surface.getVertex(i, j);
                        rgba = surface.getEdgeRGBA(i, j);
                        gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                        gl.glVertex3f(transform.transform_x((float) p.X), transform.transform_y((float) p.Y), transform.transform_z((float) p.Z));
                    }
                    gl.glEnd();
                }
            } else {
                float[] vertex;
                for (int i = 0; i < dim1; i++) {
                    p = surface.getVertex(i, 0);
                    vertex = this.transform.transformf(p);
                    for (int j = 0; j < dim2 - 1; j++) {
                        rgba = surface.getEdgeRGBA(i, j);
                        gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                        gl.glBegin(GL2.GL_LINES);
                        gl.glVertex3fv(vertex, 0);
                        p = surface.getVertex(i, j + 1);
                        vertex = this.transform.transformf(p);
                        gl.glVertex3fv(vertex, 0);
                        gl.glEnd();
                    }
                }
                for (int j = 0; j < dim2; j++) {
                    p = surface.getVertex(0, j);
                    vertex = this.transform.transformf(p);
                    for (int i = 0; i < dim1 - 1; i++) {
                        p = surface.getVertex(i, j);
                        rgba = surface.getEdgeRGBA(i, j);
                        gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                        gl.glBegin(GL2.GL_LINES);
                        gl.glVertex3fv(vertex, 0);
                        p = surface.getVertex(i + 1, j);
                        vertex = this.transform.transformf(p);
                        gl.glVertex3fv(vertex, 0);
                        gl.glEnd();
                    }
                }
            }
        }

        if (pgb.isDrawFill()) {
            gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL);
            gl.glPolygonOffset(1.0f, 1.0f);
            float[] p1, p2, p3, p4;
            if (surface.isFaceInterp()) {
                for (int i = 0; i < dim1 - 1; i++) {
                    for (int j = 0; j < dim2 - 1; j++) {
                        p1 = transform.transformf(surface.getVertex(i, j));
                        p2 = transform.transformf(surface.getVertex(i + 1, j));
                        p3 = transform.transformf(surface.getVertex(i + 1, j + 1));
                        p4 = transform.transformf(surface.getVertex(i, j + 1));
                        gl.glBegin(GL2.GL_QUADS);
                        if (usingLight) {
                            float[] normal = JOGLUtil.normalize(p1, p2, p3);
                            gl.glNormal3fv(normal, 0);
                        }
                        rgba = surface.getRGBA(i, j);
                        gl.glColor4fv(rgba, 0);
                        gl.glVertex3fv(p1, 0);
                        rgba = surface.getRGBA(i + 1, j);
                        gl.glColor4fv(rgba, 0);
                        gl.glVertex3fv(p2, 0);
                        rgba = surface.getRGBA(i + 1, j + 1);
                        gl.glColor4fv(rgba, 0);
                        gl.glVertex3fv(p3, 0);
                        rgba = surface.getRGBA(i, j + 1);
                        gl.glColor4fv(rgba, 0);
                        gl.glVertex3fv(p4, 0);
                        gl.glEnd();
                    }
                }
            } else {
                for (int i = 0; i < dim1 - 1; i++) {
                    for (int j = 0; j < dim2 - 1; j++) {
                        p1 = transform.transformf(surface.getVertex(i, j));
                        p2 = transform.transformf(surface.getVertex(i + 1, j));
                        p3 = transform.transformf(surface.getVertex(i + 1, j + 1));
                        p4 = transform.transformf(surface.getVertex(i, j + 1));
                        gl.glBegin(GL2.GL_QUADS);
                        if (usingLight) {
                            float[] normal = JOGLUtil.normalize(p1, p2, p3);
                            gl.glNormal3fv(normal, 0);
                        }
                        rgba = surface.getRGBA(i, j);
                        gl.glColor4fv(rgba, 0);
                        gl.glVertex3fv(p1, 0);
                        gl.glVertex3fv(p2, 0);
                        gl.glVertex3fv(p3, 0);
                        gl.glVertex3fv(p4, 0);
                        gl.glEnd();
                    }
                }
            }
            gl.glDisable(GL2.GL_POLYGON_OFFSET_FILL);
        }

        if (lightEnabled && !surface.isUsingLight()) {
            this.lighting.start(gl);
        }
    }

    private void drawIsosurface(GL2 gl, IsosurfaceGraphics isosurface) {
        List triangles = isosurface.getTriangles();
        PolygonBreak pgb = (PolygonBreak) isosurface.getLegendBreak();
        for (PointZ[] triangle : triangles) {
            this.drawTriangle(gl, triangle, pgb);
        }
    }

    private void drawImage(GL2 gl, Graphic graphic) {
        ImageShape ishape = (ImageShape) graphic.getShape();
        BufferedImage image = ishape.getImage();
        Texture texture = AWTTextureIO.newTexture(gl.getGLProfile(), image, true);
        //Texture texture = this.imageCache.get(image);
        int idTexture = texture.getTextureObject();
        List coords = ishape.getCoords();

        gl.glEnable(GL2.GL_TEXTURE_2D);
        gl.glColor3f(1f, 1f, 1f);
        gl.glBindTexture(GL2.GL_TEXTURE_2D, idTexture);

        // Texture parameterization
        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);

        // Draw image
        gl.glBegin(GL2.GL_QUADS);
        // Front Face
        //gl.glTexCoord2f(0.0f, 0.0f);
        gl.glTexCoord2f(0.0f, 1.0f);
        gl.glVertex3fv(transform.transform((float) coords.get(0).X, (float) coords.get(0).Y, (float) coords.get(0).Z), 0);
        //gl.glTexCoord2f(1.0f, 0.0f);
        gl.glTexCoord2f(1.0f, 1.0f);
        gl.glVertex3fv(transform.transform((float) coords.get(1).X, (float) coords.get(1).Y, (float) coords.get(1).Z), 0);
        //gl.glTexCoord2f(1.0f, 1.0f);
        gl.glTexCoord2f(1.0f, 0.0f);
        gl.glVertex3fv(transform.transform((float) coords.get(2).X, (float) coords.get(2).Y, (float) coords.get(2).Z), 0);
        //gl.glTexCoord2f(0.0f, 1.0f);
        gl.glTexCoord2f(0.0f, 0.0f);
        gl.glVertex3fv(transform.transform((float) coords.get(3).X, (float) coords.get(3).Y, (float) coords.get(3).Z), 0);
        gl.glEnd();

        // Unbinding the texture
        gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
        gl.glDisable(GL2.GL_TEXTURE_2D);
    }

    private void drawImage(GL2 gl) throws IOException {
        File im = new File("D:\\Temp\\image\\lenna.jpg ");
        BufferedImage image = ImageIO.read(im);
        Texture t = AWTTextureIO.newTexture(gl.getGLProfile(), image, true);
        //Texture t = TextureIO.newTexture(im, true);
        //Texture t = this.imageCache.get(image);
        int idTexture = t.getTextureObject(gl);

        gl.glColor3f(1f, 1f, 1f);
        gl.glBindTexture(GL2.GL_TEXTURE_2D, idTexture);

//        // Texture parameterization
//        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR_MIPMAP_LINEAR);
//        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);
        // Draw image
        gl.glBegin(GL2.GL_QUADS);
        // Front Face
        gl.glTexCoord2f(0.0f, 0.0f);
        gl.glVertex3f(-1.0f, -1.0f, 1.0f);
        gl.glTexCoord2f(1.0f, 0.0f);
        gl.glVertex3f(1.0f, -1.0f, 1.0f);
        gl.glTexCoord2f(1.0f, 1.0f);
        gl.glVertex3f(1.0f, 1.0f, 1.0f);
        gl.glTexCoord2f(0.0f, 1.0f);
        gl.glVertex3f(-1.0f, 1.0f, 1.0f);
        gl.glEnd();

        // Unbinding the texture
        gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
    }

    private void drawTexture(GL2 gl, Graphic graphic) {
        TextureShape ishape = (TextureShape) graphic.getShape();
        ishape.updateTexture(gl);
        int idTexture = ishape.getTextureID();
        List coords = ishape.getCoords();

        gl.glEnable(GL2.GL_TEXTURE_2D);
        gl.glColor3f(1f, 1f, 1f);
        gl.glBindTexture(GL2.GL_TEXTURE_2D, idTexture);

        // Texture parameterization
        //gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_NEAREST);
        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR_MIPMAP_LINEAR);
        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);

        // Draw image
        gl.glBegin(GL2.GL_QUADS);
        // Front Face
        //gl.glTexCoord2f(0.0f, 0.0f);
        gl.glTexCoord2f(0.0f, 1.0f);
        gl.glVertex3fv(transform.transform((float) coords.get(0).X, (float) coords.get(0).Y, (float) coords.get(0).Z), 0);
        //gl.glTexCoord2f(1.0f, 0.0f);
        gl.glTexCoord2f(1.0f, 1.0f);
        gl.glVertex3fv(transform.transform((float) coords.get(1).X, (float) coords.get(1).Y, (float) coords.get(1).Z), 0);
        //gl.glTexCoord2f(1.0f, 1.0f);
        gl.glTexCoord2f(1.0f, 0.0f);
        gl.glVertex3fv(transform.transform((float) coords.get(2).X, (float) coords.get(2).Y, (float) coords.get(2).Z), 0);
        //gl.glTexCoord2f(0.0f, 1.0f);
        gl.glTexCoord2f(0.0f, 0.0f);
        gl.glVertex3fv(transform.transform((float) coords.get(3).X, (float) coords.get(3).Y, (float) coords.get(3).Z), 0);
        gl.glEnd();
        gl.glFlush();

        // Unbinding the texture
        gl.glBindTexture(GL2.GL_TEXTURE_2D, 0);
        gl.glDisable(GL2.GL_TEXTURE_2D);
    }

    void drawArrow(GL2 gl, float[] p1, float[] p2, StreamlineBreak slb) {
        // Calculate vector along direction of line
        float[] v = {p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]};
        float norm_of_v = (float) Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);

        // Size of cone in arrow:
        //float coneFractionAxially = 0.025f; // radius at thickest part
        //float coneFractionRadially = 0.12f; // length of arrow

        //float coneHgt = coneFractionAxially;
        //float coneRadius = coneFractionRadially;
        float coneRadius = slb.getArrowHeadLength() * 0.02f;
        float coneHgt = slb.getArrowHeadWidth() * 0.02f;

        // Set location of arrowhead to be at the startpoint of the line
        float[] vConeLocation = p2;

        // Initialize transformation matrix
        float[] mat44
                = {1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1};

        // The direction of the arrowhead is the line vector
        float[] dVec = {v[0], v[1], v[2]};

        // Normalize dVec to get Unit Vector norm_startVec
        float[] norm_startVec = VectorUtil.normalizeVec3(dVec);

        // Normalize zaxis to get Unit Vector norm_endVec
        float[] zaxis = {0.0f, 0.0f, 1.0f};
        float[] norm_endVec = VectorUtil.normalizeVec3(zaxis);

        if (Float.isNaN(norm_endVec[0]) || Float.isNaN(norm_endVec[1]) || Float.isNaN(norm_endVec[2])) {
            norm_endVec[0] = 0.0f;
            norm_endVec[1] = 0.0f;
            norm_endVec[2] = 0.0f;
        }

        // If vectors are identical, set transformation matrix to identity
        if (((norm_startVec[0] - norm_endVec[0]) > 1e-14) && ((norm_startVec[1] - norm_endVec[1]) > 1e-14) && ((norm_startVec[2] - norm_endVec[2]) > 1e-14)) {
            mat44[0] = 1.0f;
            mat44[5] = 1.0f;
            mat44[10] = 1.0f;
            mat44[15] = 1.0f;
        } // otherwise create the matrix
        else {
            // Vector cross-product, result = axb
            float[] axb = new float[3];
            VectorUtil.crossVec3(axb, norm_startVec, norm_endVec);

            // Normalize axb to get Unit Vector norm_axb
            float[] norm_axb = VectorUtil.normalizeVec3(axb);

            if (Float.isNaN(norm_axb[0]) || Float.isNaN(norm_axb[1]) || Float.isNaN(norm_axb[2])) {
                norm_axb[0] = 0.0f;
                norm_axb[1] = 0.0f;
                norm_axb[2] = 0.0f;
            }

            // Build the rotation matrix
            float ac = (float) Math.acos(VectorUtil.dotVec3(norm_startVec, norm_endVec));

            float s = (float) Math.sin(ac);
            float c = (float) Math.cos(ac);
            float t = 1 - c;

            float x = norm_axb[0];
            float y = norm_axb[1];
            float z = norm_axb[2];

            // Fill top-left 3x3
            mat44[0] = t * x * x + c;
            mat44[1] = t * x * y - s * z;
            mat44[2] = t * x * z + s * y;

            mat44[4] = t * x * y + s * z;
            mat44[5] = t * y * y + c;
            mat44[6] = t * y * z - s * x;

            mat44[8] = t * x * z - s * y;
            mat44[9] = t * y * z + s * x;
            mat44[10] = t * z * z + c;

            mat44[15] = 1.0f;
        }

        gl.glPushMatrix();
        gl.glPushAttrib(GL2.GL_POLYGON_BIT); // includes GL_CULL_FACE
        gl.glDisable(GL2.GL_CULL_FACE); // draw from all sides

        // Translate and rotate arrowhead to correct position
        gl.glTranslatef(vConeLocation[0], vConeLocation[1], vConeLocation[2]);
        gl.glMultMatrixf(mat44, 0);

        float[] rgba = slb.getColor().getRGBComponents(null);
        gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
        GLUquadric cone_obj = glu.gluNewQuadric();
        glu.gluCylinder(cone_obj, 0, coneHgt, coneRadius, 8, 1);

        gl.glPopAttrib(); // GL_CULL_FACE
        gl.glPopMatrix();
    }

    void drawWindArrow(GL2 gl, Graphic graphic) {
        boolean isDraw = true;
        if (this.clipPlane)
            isDraw = drawExtent.intersects(graphic.getExtent());

        if (isDraw) {
            WindArrow3D shape = (WindArrow3D) graphic.getShape();
            PointBreak pb = (PointBreak) graphic.getLegend();
            PointZ sp = (PointZ) shape.getPoint();
            PointZ ep = (PointZ) shape.getEndPoint();
            float[] xyz = transform.transform((float) sp.X, (float) sp.Y, (float) sp.Z);
            float x1 = xyz[0];
            float y1 = xyz[1];
            float z1 = xyz[2];
            xyz = transform.transform((float) ep.X, (float) ep.Y, (float) ep.Z);
            float x2 = xyz[0];
            float y2 = xyz[1];
            float z2 = xyz[2];

            gl.glPushMatrix();
            gl.glPushAttrib(GL2.GL_POLYGON_BIT); // includes GL_CULL_FACE
            gl.glDisable(GL2.GL_CULL_FACE); // draw from all sides

            float[] rgba = pb.getColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glLineWidth(pb.getOutlineSize() * this.dpiScale);
            gl.glBegin(GL2.GL_LINES);
            gl.glVertex3f(x1, y1, z1);
            gl.glVertex3f(x2, y2, z2);
            gl.glEnd();

            // Calculate vector along direction of line
            float[] v = {x1 - x2, y1 - y2, z1 - z2};
            float norm_of_v = (float) Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);

            // Size of cone in arrow:
            //float coneFractionAxially = 0.025f; // radius at thickest part
            //float coneFractionRadially = 0.12f; // length of arrow

            //float coneHgt = coneFractionAxially * norm_of_v;
            //float coneRadius = coneFractionRadially * norm_of_v;
            float coneRadius = shape.getHeadLength() * 0.02f;
            float coneHgt = shape.getHeadWith() * 0.02f;

            // Set location of arrowhead to be at the startpoint of the line
            float[] vConeLocation = {x2, y2, z2};

            // Initialize transformation matrix
            float[] mat44
                    = {1, 0, 0, 0,
                        0, 1, 0, 0,
                        0, 0, 1, 0,
                        0, 0, 0, 1};

            // The direction of the arrowhead is the line vector
            float[] dVec = {v[0], v[1], v[2]};

            // Normalize dVec to get Unit Vector norm_startVec
            float[] norm_startVec = VectorUtil.normalizeVec3(dVec);

            // Normalize zaxis to get Unit Vector norm_endVec
            float[] zaxis = {0.0f, 0.0f, 1.0f};
            float[] norm_endVec = VectorUtil.normalizeVec3(zaxis);

            if (Float.isNaN(norm_endVec[0]) || Float.isNaN(norm_endVec[1]) || Float.isNaN(norm_endVec[2])) {
                norm_endVec[0] = 0.0f;
                norm_endVec[1] = 0.0f;
                norm_endVec[2] = 0.0f;
            }

            // If vectors are identical, set transformation matrix to identity
            if (((norm_startVec[0] - norm_endVec[0]) > 1e-14) && ((norm_startVec[1] - norm_endVec[1]) > 1e-14) && ((norm_startVec[2] - norm_endVec[2]) > 1e-14)) {
                mat44[0] = 1.0f;
                mat44[5] = 1.0f;
                mat44[10] = 1.0f;
                mat44[15] = 1.0f;
            } // otherwise create the matrix
            else {
                // Vector cross-product, result = axb
                float[] axb = new float[3];
                VectorUtil.crossVec3(axb, norm_startVec, norm_endVec);

                // Normalize axb to get Unit Vector norm_axb
                float[] norm_axb = VectorUtil.normalizeVec3(axb);

                if (Float.isNaN(norm_axb[0]) || Float.isNaN(norm_axb[1]) || Float.isNaN(norm_axb[2])) {
                    norm_axb[0] = 0.0f;
                    norm_axb[1] = 0.0f;
                    norm_axb[2] = 0.0f;
                }

                // Build the rotation matrix
                float ac = (float) Math.acos(VectorUtil.dotVec3(norm_startVec, norm_endVec));

                float s = (float) Math.sin(ac);
                float c = (float) Math.cos(ac);
                float t = 1 - c;

                float x = norm_axb[0];
                float y = norm_axb[1];
                float z = norm_axb[2];

                // Fill top-left 3x3
                mat44[0] = t * x * x + c;
                mat44[1] = t * x * y - s * z;
                mat44[2] = t * x * z + s * y;

                mat44[4] = t * x * y + s * z;
                mat44[5] = t * y * y + c;
                mat44[6] = t * y * z - s * x;

                mat44[8] = t * x * z - s * y;
                mat44[9] = t * y * z + s * x;
                mat44[10] = t * z * z + c;

                mat44[15] = 1.0f;
            }

            // Translate and rotate arrowhead to correct position
            gl.glTranslatef(vConeLocation[0], vConeLocation[1], vConeLocation[2]);
            gl.glMultMatrixf(mat44, 0);

            GLUquadric cone_obj = glu.gluNewQuadric();
            glu.gluCylinder(cone_obj, 0, coneHgt, coneRadius, 8, 1);

            gl.glPopAttrib(); // GL_CULL_FACE
            gl.glPopMatrix();
        }
    }

    void drawCircle(GL2 gl, float z, float radius, PolygonBreak bb) {
        drawCircle(gl, z, radius, bb, false);
    }

    void drawCircle(GL2 gl, float z, float radius, PolygonBreak bb, boolean clockwise) {
        int points = 100;
        List vertex = new ArrayList<>();
        double angle = 0.0;
        if (clockwise) {
            for (int i = points - 1; i >= 0; i--) {
                angle = 2 * Math.PI * i / points;
                vertex.add(new float[]{(float) Math.cos(angle) * radius, (float) Math.sin(angle) * radius, z});
            }
        } else {
            for (int i = 0; i < points; i++) {
                angle = 2 * Math.PI * i / points;
                vertex.add(new float[]{(float) Math.cos(angle) * radius, (float) Math.sin(angle) * radius, z});
            }
        }

        if (bb.isDrawFill()) {
            float[] rgba = bb.getColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_TRIANGLE_FAN);
            if (this.lighting.isEnable()) {
                int ii = points / 4;
                float[] normal;
                normal = JOGLUtil.normalize(vertex.get(0), vertex.get(ii), vertex.get(ii * 2));
                gl.glNormal3fv(normal, 0);
            }
            for(int i =0; i < points;i++){
                gl.glVertex3f(vertex.get(i)[0], vertex.get(i)[1], vertex.get(i)[2]);
            }
            gl.glEnd();
        }

        if (bb.isDrawOutline()) {
            float[] rgba = bb.getOutlineColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glLineWidth(bb.getOutlineSize() * this.dpiScale);
            gl.glBegin(GL2.GL_LINE_LOOP);
            for (int i = 0; i < points; i++) {
                gl.glVertex3f(vertex.get(i)[0], vertex.get(i)[1], vertex.get(i)[2]);
            }
            gl.glEnd();
        }
    }

    void drawCubic(GL2 gl, Graphic graphic) {
        boolean isDraw = true;
        if (this.clipPlane)
            isDraw = drawExtent.intersects(graphic.getExtent());

        if (isDraw) {
            CubicShape cubic = (CubicShape) graphic.getShape();
            BarBreak bb = (BarBreak) graphic.getLegend();
            List ps = cubic.getPoints();
            List vertex = new ArrayList<>();
            for (PointZ p : ps) {
                vertex.add(transform.transform((float) p.X, (float) p.Y, (float) p.Z));
            }

            gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL);
            gl.glPolygonOffset(1.0f, 1.0f);
            int[][] index = cubic.getIndex();
            float[] rgba = bb.getColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_QUADS);
            for (int[] ii : index) {
                if (this.lighting.isEnable()) {
                    float[] normal = JOGLUtil.normalize(vertex.get(ii[0]), vertex.get(ii[1]), vertex.get(ii[2]));
                    gl.glNormal3fv(normal, 0);
                }
                for (int i : ii) {
                    gl.glVertex3f(vertex.get(i)[0], vertex.get(i)[1], vertex.get(i)[2]);
                }
            }
            gl.glEnd();
            gl.glDisable(GL2.GL_POLYGON_OFFSET_FILL);

            if (bb.isDrawOutline()) {
                rgba = bb.getOutlineColor().getRGBComponents(null);
                gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                gl.glLineWidth(bb.getOutlineSize() * this.dpiScale);
                gl.glBegin(GL2.GL_LINES);
                for (int[] ii : cubic.getLineIndex()) {
                    for (int i : ii) {
                        gl.glVertex3f(vertex.get(i)[0], vertex.get(i)[1], vertex.get(i)[2]);
                    }
                }
                gl.glEnd();
            }
        }
    }

    void drawCylinder(GL2 gl, Graphic graphic) {
        boolean isDraw = true;
        if (this.clipPlane)
            isDraw = drawExtent.intersects(graphic.getExtent());

        if (isDraw) {
            CylinderShape cylinder = (CylinderShape) graphic.getShape();
            BarBreak bb = (BarBreak) graphic.getLegend();
            List ps = cylinder.getPoints();
            List vertex = new ArrayList<>();
            for (PointZ p : ps) {
                vertex.add(transform.transform((float) p.X, (float) p.Y, (float) p.Z));
            }
            double height = vertex.get(1)[2] - vertex.get(0)[2];

            gl.glPushMatrix();
            gl.glPushAttrib(GL2.GL_POLYGON_BIT); // includes GL_CULL_FACE
            gl.glDisable(GL2.GL_CULL_FACE); // draw from all sides

            float[] rgba = bb.getColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glTranslatef(vertex.get(0)[0], vertex.get(0)[1], vertex.get(0)[2]);
            GLUquadric cone_obj = glu.gluNewQuadric();
            glu.gluCylinder(cone_obj, cylinder.getRadius(), cylinder.getRadius(), height, 100, 1);
            bb.setDrawOutline(false);
            this.drawCircle(gl, (float) height, (float) cylinder.getRadius(), bb);
            this.drawCircle(gl, 0.f, (float) cylinder.getRadius(), bb, true);

            gl.glPopAttrib(); // GL_CULL_FACE
            gl.glPopMatrix();
        }
    }

    void drawLegend(GL2 gl, ChartColorBar legend) {
        LegendScheme ls = legend.getLegendScheme();
        int bNum = ls.getBreakNum();
        if (ls.getLegendBreaks().get(bNum - 1).isNoData()) {
            bNum -= 1;
        }

        float x = 1.6f;
        x += legend.getXShift() * this.lenScale;
        float y = -1.0f;
        float lHeight = 2.0f;
        float lWidth = lHeight / legend.getAspect();
        List labelIdxs = new ArrayList<>();
        List tLabels = new ArrayList<>();
        if (legend.isAutoTick()) {
            float legendLen = this.toScreenLength(x, y, 0.0f, x, y + lHeight, 0.0f);
            int tickGap = this.getLegendTickGap(legend, legendLen);
            int sIdx = (bNum % tickGap) / 2;
            int labNum = bNum - 1;
            if (ls.getLegendType() == LegendType.UNIQUE_VALUE) {
                labNum += 1;
            } else if (legend.isDrawMinLabel()) {
                sIdx = 0;
                labNum = bNum;
            }
            while (sIdx < labNum) {
                labelIdxs.add(sIdx);
                sIdx += tickGap;
            }
        } else {
            int tickIdx;
            for (int i = 0; i < bNum; i++) {
                ColorBreak cb = ls.getLegendBreaks().get(i);
                double v = Double.parseDouble(cb.getEndValue().toString());
                if (legend.getTickLocations().contains(v)) {
                    labelIdxs.add(i);
                    tickIdx = legend.getTickLocations().indexOf(v);
                    tLabels.add(legend.getTickLabels().get(tickIdx).getText());
                }
            }
        }

        float barHeight = lHeight / bNum;

        //Draw color bar
        gl.glDepthFunc(GL.GL_ALWAYS);
        float yy = y;
        float[] rgba;
        for (int i = 0; i < bNum; i++) {
            //Fill color
            rgba = ls.getLegendBreak(i).getColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_QUADS);
            gl.glVertex2f(x, yy);
            gl.glVertex2f(x + lWidth, yy);
            gl.glVertex2f(x + lWidth, yy + barHeight);
            gl.glVertex2f(x, yy + barHeight);
            gl.glEnd();
            yy += barHeight;
        }

        //Draw neatline
        rgba = legend.getTickColor().getRGBComponents(null);
        gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
        gl.glLineWidth(legend.getNeatLineSize() * this.dpiScale);
        gl.glBegin(GL2.GL_LINE_STRIP);
        gl.glVertex2f(x, y);
        gl.glVertex2f(x, y + lHeight);
        gl.glVertex2f(x + lWidth, y + lHeight);
        gl.glVertex2f(x + lWidth, y);
        gl.glVertex2f(x, y);
        gl.glEnd();

        //Draw ticks
        int idx = 0;
        yy = y;
        String caption;
        float tickLen = legend.getTickLength() * this.lenScale;
        float labelX = x + lWidth;
        if (legend.isInsideTick()) {
            if (tickLen > lWidth)
                tickLen = lWidth;
        } else {
            labelX += tickLen;
        }
        float strWidth = 0;
        Rectangle2D rect;
        float xShift = this.tickSpace * this.dpiScale;
        for (int i = 0; i < bNum; i++) {
            if (labelIdxs.contains(i)) {
                ColorBreak cb = ls.getLegendBreaks().get(i);
                if (legend.isAutoTick()) {
                    if (ls.getLegendType() == LegendType.UNIQUE_VALUE) {
                        caption = cb.getCaption();
                    } else {
                        caption = DataConvert.removeTailingZeros(cb.getEndValue().toString());
                    }
                } else {
                    caption = tLabels.get(idx);
                }
                if (ls.getLegendType() == LegendType.UNIQUE_VALUE) {
                    rect = this.drawString(gl, caption, legend.getTickLabelFont(), legend.getTickLabelColor(),
                            x + lWidth, yy + barHeight * 0.5f, 0, XAlign.LEFT, YAlign.CENTER, xShift, 0);
                } else {
                    rgba = legend.getTickColor().getRGBComponents(null);
                    gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
                    gl.glLineWidth(legend.getTickWidth() * this.dpiScale);
                    gl.glBegin(GL2.GL_LINES);
                    if (legend.isInsideTick())
                        gl.glVertex2f(x + lWidth - tickLen, yy + barHeight);
                    else
                        gl.glVertex2f(x + lWidth + tickLen, yy + barHeight);
                    gl.glVertex2f(x + lWidth, yy + barHeight);
                    gl.glEnd();
                    rect = this.drawString(gl, caption, legend.getTickLabelFont(), legend.getTickLabelColor(),
                            labelX, yy + barHeight, 0, XAlign.LEFT, YAlign.CENTER, xShift, 0);
                }
                if (strWidth < rect.getWidth())
                    strWidth = (float) rect.getWidth();

                idx += 1;
            }
            yy += barHeight;
        }

        //Draw label
        ChartText label = legend.getLabel();
        if (label != null) {
            label.setColor(legend.getTickColor());
            float sx, sy;
            float yShift = this.tickSpace * this.dpiScale;
            switch (legend.getLabelLocation()) {
                case "top":
                    sx = x + lWidth * 0.5f;
                    sy = y + lHeight;
                    drawString(gl, label, sx, sy, 0.0f, XAlign.CENTER, YAlign.BOTTOM, 0, 0, yShift);
                    break;
                case "bottom":
                    sx = x + lWidth * 0.5f;
                    sy = y;
                    yShift = -yShift;
                    drawString(gl, label, sx, sy, 0.0f, XAlign.CENTER, YAlign.TOP, 0, 0, yShift);
                    break;
                case "left":
                case "in":
                    sx = x;
                    sy = y + lHeight * 0.5f;
                    drawString(gl, label, sx, sy, 0.0f, XAlign.CENTER, YAlign.BOTTOM, 90.f, 0, yShift);
                    break;
                default:
                    sx = labelX;
                    sy = y + lHeight * 0.5f;
                    yShift = -strWidth - yShift;
                    drawString(gl, label, sx, sy, 0.0f, XAlign.CENTER, YAlign.TOP, 90.f, 0, yShift);
                    break;
            }
        }
        gl.glDepthFunc(GL2.GL_LEQUAL);
    }

    void drawColorbar(GL2 gl, ChartColorBar legend) {
        LegendScheme ls = legend.getLegendScheme();
        ColorMap colorMap = ls.getColorMap();
        Normalize normalize = ls.getNormalize();
        int bNum = colorMap.getColorCount();
        if (normalize instanceof BoundaryNorm) {
            bNum = ((BoundaryNorm) normalize).getNRegions();
        }

        float x = 1.6f;
        x += legend.getXShift() * this.lenScale;
        float y = -1.0f;
        float legendHeight = 2.0f;
        float barWidth = legendHeight / legend.getAspect();
        float minMaxHeight = legendHeight;
        float y_shift = 0;
        switch (legend.getExtendType()) {
            case MIN:
                minMaxHeight -= barWidth;
                y_shift += barWidth;
                break;
            case MAX:
                minMaxHeight -= barWidth;
                break;
            case BOTH:
                minMaxHeight -= barWidth * 2;
                y_shift += barWidth;
                break;
        }
        float barHeight = minMaxHeight / bNum;

        //Draw color bar
        gl.glDepthFunc(GL.GL_ALWAYS);
        float yy = y;
        float[] rgba;
        Color[] colors = colorMap.getColors(bNum);
        for (int i = 0; i < bNum; i++) {
            //Fill color
            rgba = colors[i].getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glBegin(GL2.GL_QUADS);
            gl.glVertex2f(x, yy);
            gl.glVertex2f(x + barWidth, yy);
            gl.glVertex2f(x + barWidth, yy + barHeight);
            gl.glVertex2f(x, yy + barHeight);
            gl.glEnd();
            yy += barHeight;
        }

        //Draw neatline
        rgba = legend.getTickColor().getRGBComponents(null);
        gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
        gl.glLineWidth(legend.getNeatLineSize() * this.dpiScale);
        gl.glBegin(GL2.GL_LINE_STRIP);
        gl.glVertex2f(x, y);
        gl.glVertex2f(x, y + legendHeight);
        gl.glVertex2f(x + barWidth, y + legendHeight);
        gl.glVertex2f(x + barWidth, y);
        gl.glVertex2f(x, y);
        gl.glEnd();

        //Draw ticks
        int idx = 0;
        yy = y;
        String caption;
        float tickLen = legend.getTickLength() * this.lenScale;
        float labelX = x + barWidth;
        if (legend.isInsideTick()) {
            if (tickLen > barWidth)
                tickLen = barWidth;
        } else {
            labelX += tickLen;
        }
        float strWidth = 0;
        Rectangle2D rect;
        float xShift = this.tickSpace * this.dpiScale;
        for (int i = 0; i < legend.getTickLocations().size(); i++) {
            yy = y + minMaxHeight * normalize.apply(legend.getTickLocations().get(i)).floatValue();
            String label = legend.getTickLabels().get(i).getText();
            rgba = legend.getTickColor().getRGBComponents(null);
            gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]);
            gl.glLineWidth(legend.getTickWidth() * this.dpiScale);
            gl.glBegin(GL2.GL_LINES);
            if (legend.isInsideTick())
                gl.glVertex2f(x + barWidth - tickLen, yy);
            else
                gl.glVertex2f(x + barWidth + tickLen, yy);
            gl.glVertex2f(x + barWidth, yy);
            gl.glEnd();
            rect = this.drawString(gl, label, legend.getTickLabelFont(), legend.getTickLabelColor(),
                    labelX, yy, 0, XAlign.LEFT, YAlign.CENTER, xShift, 0);
            if (strWidth < rect.getWidth())
                strWidth = (float) rect.getWidth();
        }

        //Draw label
        ChartText label = legend.getLabel();
        if (label != null) {
            label.setColor(legend.getTickColor());
            float sx, sy;
            float yShift = this.tickSpace * this.dpiScale;
            switch (legend.getLabelLocation()) {
                case "top":
                    sx = x + barWidth * 0.5f;
                    sy = y + legendHeight;
                    drawString(gl, label, sx, sy, 0.0f, XAlign.CENTER, YAlign.BOTTOM, 0, 0, yShift);
                    break;
                case "bottom":
                    sx = x + barWidth * 0.5f;
                    sy = y;
                    yShift = -yShift;
                    drawString(gl, label, sx, sy, 0.0f, XAlign.CENTER, YAlign.TOP, 0, 0, yShift);
                    break;
                case "left":
                case "in":
                    sx = x;
                    sy = y + legendHeight * 0.5f;
                    drawString(gl, label, sx, sy, 0.0f, XAlign.CENTER, YAlign.BOTTOM, 90.f, 0, yShift);
                    break;
                default:
                    sx = labelX;
                    sy = y + legendHeight * 0.5f;
                    yShift = -strWidth - yShift;
                    drawString(gl, label, sx, sy, 0.0f, XAlign.CENTER, YAlign.TOP, 90.f, 0, yShift);
                    break;
            }
        }
        gl.glDepthFunc(GL2.GL_LEQUAL);
    }

    /**
     * Get legend scheme
     *
     * @return Legend scheme
     */
    public LegendScheme getLegendScheme() {
        LegendScheme ls = null;
        int n = this.graphics.getNumGraphics();
        for (int i = n - 1; i >= 0; i--) {
            Graphic g = this.graphics.getGraphicN(i);
            if (g instanceof GraphicCollection) {
                ls = ((GraphicCollection)g).getLegendScheme();
                break;
            }
        }

        if (ls == null) {
            ShapeTypes stype = ShapeTypes.POLYLINE;
            ls = new LegendScheme(stype);
            for (Graphic g : this.graphics.getGraphics()) {
                ls.getLegendBreaks().add(g.getLegend());
            }
        }
        return ls;
    }

    /**
     * Get extent scale - extent / draw extent
     * @return Extent scale
     */
    public float getScale() {
        return (float) (this.extent.getWidth() / this.drawExtent.getWidth());
    }

    @Override
    public void dispose(GLAutoDrawable drawable) {
        GL2 gl = drawable.getGL().getGL2();
        /*if (this.volumeRender != null) {
            this.volumeRender.dispose();
        }*/
        Program.destroyAllPrograms(gl);
    }

    @Override
    public void init(GLAutoDrawable drawable) {
        this.drawable = drawable;
        //drawable.getContext().makeCurrent();
        GL2 gl = drawable.getGL().getGL2();
        this.gl = gl;
        //Background
        //gl.glClearColor(1f, 1f, 1f, 1.0f);
        gl.glEnable(GL2.GL_POINT_SMOOTH);
        gl.glEnable(GL2.GL_DEPTH_TEST);
        gl.glShadeModel(GL2.GL_SMOOTH);
        //gl.glShadeModel(GL2.GL_FLAT);
        gl.glDepthFunc(GL2.GL_LEQUAL);
        //gl.glDepthFunc(GL2.GL_LESS);
        gl.glHint(GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL2.GL_NICEST);

        //jogl specific addition for tessellation
        tessCallback = new TessCallback(gl, glu);

        this.positionArea = new Rectangle2D.Double(0, 0, 1, 1);
    }

    @Override
    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        this.width = width;
        this.height = height;
        this.positionArea = this.getPositionArea(new Rectangle2D.Double(0, 0, width, height));

        final GL2 gl = drawable.getGL().getGL2();
        if (height <= 0) {
            height = 1;
        }

        final float h = (float) width / (float) height;
        gl.glViewport(0, 0, width, height);
        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();

        if (this.orthographic) {
            float v = 2.0f;
            switch (this.aspectType) {
                case EQUAL:
                    gl.glOrthof(-v * h, v * h, -v, v, -distance, distance);
                    break;
                default:
                    gl.glOrthof(-v, v, -v, v, -distance, distance);
                    break;
            }
        } else {
            float near = 0.1f;
            float far = 1000.0f;
            switch (this.aspectType) {
                case EQUAL:
                    glu.gluPerspective(45.0f, h, near, far);
                    break;
                default:
                    glu.gluPerspective(45.0f, 1.0f, near, far);
                    break;
            }
            glu.gluLookAt(0.0f, 0.0f, distance, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
        }
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();
    }

    /**
     * Get tight inset area
     *
     * @param g Graphics2D
     * @param positionArea Position area
     * @return Tight inset area
     */
    @Override
    public Margin getTightInset(Graphics2D g, Rectangle2D positionArea) {
        return null;
    }

    /**
     * Clone
     * @return Cloned Plot3DGL
     */
    public Object clone() {
        Plot3DGL plot3DGL = new Plot3DGL();
        plot3DGL.graphics = this.graphics;
        plot3DGL.angleX = this.angleX;
        plot3DGL.angleY = this.angleY;
        plot3DGL.background = this.background;
        plot3DGL.sampleBuffers = this.sampleBuffers;
        plot3DGL.antialias = this.antialias;
        plot3DGL.boxColor = this.boxColor;
        plot3DGL.boxed = this.boxed;
        plot3DGL.clipPlane = this.clipPlane;
        plot3DGL.displayXY = this.displayXY;
        plot3DGL.displayZ = this.displayZ;
        plot3DGL.dpiScale = this.dpiScale;
        plot3DGL.drawBase = this.drawBase;
        plot3DGL.drawBoundingBox = this.drawBoundingBox;
        plot3DGL.setDrawExtent((Extent3D) this.drawExtent.clone());
        plot3DGL.gridLine = this.gridLine;
        plot3DGL.legends = this.legends;
        plot3DGL.hideOnDrag = this.hideOnDrag;
        plot3DGL.lighting = this.lighting;
        plot3DGL.title = this.title;

        return plot3DGL;
    }

    public static void main(String[] args) {

        final GLProfile gp = GLProfile.get(GLProfile.GL2);
        GLCapabilities cap = new GLCapabilities(gp);

        final GLChartPanel gc = new GLChartPanel(cap, new Plot3DGL());
        gc.setSize(400, 400);

        final JFrame frame = new JFrame("JOGL Line");
        frame.add(gc);
        frame.setSize(500, 400);
        frame.setVisible(true);

        //gc.animator_start();
    }

    // 

    public static class TessCallback extends com.jogamp.opengl.glu.GLUtessellatorCallbackAdapter {
        GL2 gl;
        GLU glu;

        public TessCallback(GL2 gl, GLU glu) {
            this.gl = gl;
            this.glu = glu;
        };
        public void begin(int type) {
            gl.glBegin(type);
        }

        public void end() {
            gl.glEnd();
        }

        public void vertex(Object data) {
            if (data instanceof double[]) {
                double[] d = (double[]) data;
                if (d.length == 6) {
                    gl.glColor3dv(d, 3);
                }
                gl.glVertex3dv(d, 0);
            }
        }

        public void error(int errnum) {
            String estring;
            estring = glu.gluErrorString(errnum);
            System.out.println("Tessellation Error: " + estring);
            //System.exit(0);
            throw new RuntimeException();
        }

        public void combine(double[] coords, Object[] data,
                            float[] weight, Object[] outData) {
            double[] vertex = new double[6];

            int i;
            vertex[0] = coords[0];
            vertex[1] = coords[1];
            vertex[2] = coords[2];
            for (i = 3; i < 6; i++)
                vertex[i] = weight[0] * ((double[]) data[0])[i] +
                        weight[1] * ((double[]) data[1])[i] +
                        weight[2] * ((double[]) data[2])[i] +
                        weight[3] * ((double[]) data[3])[i];
            outData[0] = vertex;
        }
    }//End TessCallback
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy