/*******************************************************************************
 * Copyright (c) 2010 Haifeng Li
 *   
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package smile.plot;

import java.awt.Color;
import smile.math.Math;
import smile.sort.QuickSort;

/**
 * A surface object gives 3D information e.g. a contour plots.
 * 
 * @author Haifeng Li
 */
public class Surface extends Plot {

    /**
     * The data-axis locations of surface.
     */
    private double[][][] data;
    /**
     * Vertex Z-axis value in camera coordinate.
     */
    private double[][] zc;
    /**
     * Average z-axis value of triangles to fill for painter's algorithm.
     */
    private double[] az;
    /**
     * The indices of triangles in descending order of average z-axis values.
     */
    private int[] order;
    /**
     * Triangles. Each row is the index of triangle vertices in data.
     */
    private int[][] triangles;
    /**
     * The minimum of the data.
     */
    private double min;
    /**
     * The maximum of the data.
     */
    private double max;
    /**
     * The window width of values for each color.
     */
    private double width = 1.0;
    /**
     * The color palette to represent values.
     */
    private Color[] palette;

    /**
     * Constructor.
     * @param z the z-axis values of surface. The x-axis and y-axis location of
     * surface will be set to 0.5, 1.5, 2.5, ...
     */
    public Surface(double[][] z) {
        super(Color.GRAY);
        init(z);
    }
    
    /**
     * Initialization.
     */
    private void init(double[][] z) {
        max = Math.max(z);
        min = Math.min(z);
        if (palette != null) {
            width = (max - min) / palette.length;
        }

        int m = z.length;
        int n = z[0].length;
        triangles = new int[2 * m * n][6];
        az = new double[2 * m * n];
        order = new int[az.length];

        zc = new double[m][n];
        data = new double[m][n][3];
        for (int i = 0, k = 0; i < m; i++) {
            for (int j = 0; j < n; j++, k += 2) {
                data[i][j][0] = i + 0.5;
                data[i][j][1] = j + 0.5;
                data[i][j][2] = z[i][j];

                if (i < m - 1 && j < n - 1) {
                    triangles[k][0] = i;
                    triangles[k][1] = j;
                    triangles[k][2] = i + 1;
                    triangles[k][3] = j;
                    triangles[k][4] = i;
                    triangles[k][5] = j + 1;

                    triangles[k + 1][0] = i + 1;
                    triangles[k + 1][1] = j + 1;
                    triangles[k + 1][2] = i + 1;
                    triangles[k + 1][3] = j;
                    triangles[k + 1][4] = i;
                    triangles[k + 1][5] = j + 1;
                }
            }
        }
    }

    /**
     * Constructor.
     * @param z the z-axis values of surface. The x-axis and y-axis location of
     * surface will be set to 0.5, 1.5, 2.5, ...
     * @param palette the color palette.
     */
    public Surface(double[][] z, Color[] palette) {
        this.palette = palette;
        init(z);
    }
    
    /**
     * Constructor for regular mesh grid.
     * @param x the x-axis values of surface.
     * @param y the y-axis values of surface.
     * @param z the z-axis values of surface.
     */
    public Surface(double[] x, double[] y, double[][] z) {
        super(Color.GRAY);
        init(x, y, z);
    }
    
    /**
     * Initialization.
     */
    private void init(double[] x, double[] y, double[][] z) {
        max = Math.max(z);
        min = Math.min(z);
        if (palette != null) {
            width = (max - min) / palette.length;
        }

        int m = z.length;
        int n = z[0].length;
        triangles = new int[2 * m * n][6];
        az = new double[2 * m * n];
        order = new int[az.length];

        zc = new double[m][n];
        data = new double[m][n][3];
        for (int i = 0, k = 0; i < m; i++) {
            for (int j = 0; j < n; j++, k += 2) {
                data[i][j][0] = x[i];
                data[i][j][1] = y[j];
                data[i][j][2] = z[i][j];

                if (i < m - 1 && j < n - 1) {
                    triangles[k][0] = i;
                    triangles[k][1] = j;
                    triangles[k][2] = i + 1;
                    triangles[k][3] = j;
                    triangles[k][4] = i;
                    triangles[k][5] = j + 1;

                    triangles[k + 1][0] = i + 1;
                    triangles[k + 1][1] = j + 1;
                    triangles[k + 1][2] = i + 1;
                    triangles[k + 1][3] = j;
                    triangles[k + 1][4] = i;
                    triangles[k + 1][5] = j + 1;
                }
            }
        }
    }

    /**
     * Constructor for regular mesh grid.
     * @param x the x-axis values of surface.
     * @param y the y-axis values of surface.
     * @param z the z-axis values of surface.
     * @param palette the color palette.
     */
    public Surface(double[] x, double[] y, double[][] z, Color[] palette) {
        this.palette = palette;
        init(x, y, z);
    }

    /**
     * Constructor for irregular mesh grid.
     * @param data an m x n x 3 array which are coordinates of m x n surface.
     */
    public Surface(double[][][] data) {
        super(Color.GRAY);
        init(data);
    }
    
    /**
     * Initialization.
     */
    private void init(double[][][] data) {
        this.data = data;

        int m = data.length;
        int n = data[0].length;
        zc = new double[m][n];

        triangles = new int[2 * m * n][6];
        az = new double[2 * m * n];
        order = new int[az.length];

        min = Double.POSITIVE_INFINITY;
        max = Double.NEGATIVE_INFINITY;
        for (int i = 0, k = 0; i < m; i++) {
            for (int j = 0; j < n; j++, k += 2) {
                double z = data[i][j][2];
                if (z < min) {
                    min = z;
                }
                if (z > max) {
                    max = z;
                }

                if (i < m - 1 && j < n - 1) {
                    triangles[k][0] = i;
                    triangles[k][1] = j;
                    triangles[k][2] = i + 1;
                    triangles[k][3] = j;
                    triangles[k][4] = i;
                    triangles[k][5] = j + 1;

                    triangles[k + 1][0] = i + 1;
                    triangles[k + 1][1] = j + 1;
                    triangles[k + 1][2] = i + 1;
                    triangles[k + 1][3] = j;
                    triangles[k + 1][4] = i;
                    triangles[k + 1][5] = j + 1;
                }
            }
        }
        
        if (palette != null) {
            width = (max - min) / palette.length;
        }

    }

    /**
     * Constructor for irregular mesh grid.
     * @param data an m x n x 3 array which are coordinates of m x n surface.
     */
    public Surface(double[][][] data, Color[] palette) {
        this.palette = palette;
        init(data);
    }

    @Override
    public void paint(Graphics g) {
        Color c = g.getColor();
        g.setColor(getColor());

        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[i].length - 1; j++) {
                g.drawLine(data[i][j], data[i][j + 1]);
            }
        }

        for (int i = 0; i < data.length - 1; i++) {
            for (int j = 0; j < data[i].length; j++) {
                g.drawLine(data[i][j], data[i + 1][j]);
            }
        }

        if (palette != null) {
            int m = data.length;
            int n = data[0].length;
            Projection3D p3d = (Projection3D) g.projection;

            /**
             * Calculates z-axis values in camera coordinates.
             */
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    zc[i][j] = p3d.z(data[i][j]);
                }
            }

            /**
             * Calculate (average) z-value for each triangle.
             * Note that this is actually just sum, which is sufficient
             * for us to sort them.
             */
            for (int i = 0; i < triangles.length; i++) {
                az[i] = (zc[triangles[i][0]][triangles[i][1]] + zc[triangles[i][2]][triangles[i][3]] + zc[triangles[i][4]][triangles[i][5]]);
            }

            /**
             * Sorts triangles by z-values and paint them from furthest to
             * nearest, i.e. painter's algorithm. Although the painter's
             * algorithm is computationally and conceptually much easier than
             * most alternatives, it does suffer from several flaws. The most
             * obvious example of where the painter's algorithm falls short
             * is with intersecting surfaces.
             */
            for (int i = 0; i < order.length; i++) {
                order[i] = i;
            }
            QuickSort.sort(az, order);

            for (int i : order) {
                double avg = (data[triangles[i][0]][triangles[i][1]][2] + data[triangles[i][2]][triangles[i][3]][2] + data[triangles[i][4]][triangles[i][5]][2]) / 3.0;
                int k = (int) ((avg - min) / width);
                if (k == palette.length) {
                    k = palette.length - 1;
                }

                g.setColor(palette[k]);
                g.fillPolygon(data[triangles[i][0]][triangles[i][1]], data[triangles[i][2]][triangles[i][3]], data[triangles[i][4]][triangles[i][5]]);
            }
        }
        
        g.setColor(c);
    }

    /**
     * Create a plot canvas with the 3D surface plot of given data.
     * @param z the z-axis values of surface. The x-axis and y-axis location of
     * surface will be set to 0.5, 1.5, 2.5, ...
     */
    public static PlotCanvas plot(double[][] z) {
        double[] lowerBound = {0, 0, Math.min(z)};
        double[] upperBound = {z.length, z[0].length, Math.max(z)};

        PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound);

        Surface surface = new Surface(z);
        canvas.add(surface);

        return canvas;
    }

    /**
     * Create a plot canvas with the 3D surface plot of given data.
     * @param z the z-axis values of surface. The x-axis and y-axis location of
     * surface will be set to 0.5, 1.5, 2.5, ...
     * @param palette the color palette.
     */
    public static PlotCanvas plot(double[][] z, Color[] palette) {
        double[] lowerBound = {0, 0, Math.min(z)};
        double[] upperBound = {z.length, z[0].length, Math.max(z)};

        PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound);

        Surface surface = new Surface(z, palette);
        canvas.add(surface);

        return canvas;
    }

    /**
     * Create a plot canvas with the 3D surface plot of given data.
     * @param x the x-axis values of surface.
     * @param y the y-axis values of surface.
     * @param z the z-axis values of surface.
     */
    public static PlotCanvas plot(double[] x, double[] y, double[][] z) {
        double[] lowerBound = {Math.min(x), Math.min(y), Math.min(z)};
        double[] upperBound = {Math.max(x), Math.max(y), Math.max(z)};

        PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound);

        Surface surface = new Surface(x, y, z);
        canvas.add(surface);

        return canvas;
    }

    /**
     * Create a plot canvas with the 3D surface plot of given data.
     * @param x the x-axis values of surface.
     * @param y the y-axis values of surface.
     * @param z the z-axis values of surface.
     * @param palette the color palette.
     */
    public static PlotCanvas plot(double[] x, double[] y, double[][] z, Color[] palette) {
        double[] lowerBound = {Math.min(x), Math.min(y), Math.min(z)};
        double[] upperBound = {Math.max(x), Math.max(y), Math.max(z)};

        PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound);

        Surface surface = new Surface(x, y, z, palette);
        canvas.add(surface);

        return canvas;
    }

    /**
     * Create a plot canvas with the 3D surface plot of given data.
     * @param data an m x n x 3 array which are coordinates of m x n surface.
     */
    public static PlotCanvas plot(double[][][] data) {
        double[] lowerBound = {data[0][0][0], data[0][0][1], data[0][0][2]};
        double[] upperBound = {data[0][0][0], data[0][0][1], data[0][0][2]};
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[i].length; j++) {
                if (data[i][j][0] < lowerBound[0]) {
                    lowerBound[0] = data[i][j][0];
                }
                if (data[i][j][0] > upperBound[0]) {
                    upperBound[0] = data[i][j][0];
                }
                if (data[i][j][1] < lowerBound[1]) {
                    lowerBound[1] = data[i][j][1];
                }
                if (data[i][j][1] > upperBound[1]) {
                    upperBound[1] = data[i][j][1];
                }
                if (data[i][j][2] < lowerBound[2]) {
                    lowerBound[2] = data[i][j][2];
                }
                if (data[i][j][2] > upperBound[2]) {
                    upperBound[2] = data[i][j][2];
                }
            }
        }

        PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound);

        Surface surface = new Surface(data);
        canvas.add(surface);

        return canvas;
    }

    /**
     * Create a plot canvas with the 3D surface plot of given data.
     * @param data an m x n x 3 array which are coordinates of m x n surface.
     * @param palette the color palette.
     */
    public static PlotCanvas plot(double[][][] data, Color[] palette) {
        double[] lowerBound = {data[0][0][0], data[0][0][1], data[0][0][2]};
        double[] upperBound = {data[0][0][0], data[0][0][1], data[0][0][2]};
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[i].length; j++) {
                if (data[i][j][0] < lowerBound[0]) {
                    lowerBound[0] = data[i][j][0];
                }
                if (data[i][j][0] > upperBound[0]) {
                    upperBound[0] = data[i][j][0];
                }
                if (data[i][j][1] < lowerBound[1]) {
                    lowerBound[1] = data[i][j][1];
                }
                if (data[i][j][1] > upperBound[1]) {
                    upperBound[1] = data[i][j][1];
                }
                if (data[i][j][2] < lowerBound[2]) {
                    lowerBound[2] = data[i][j][2];
                }
                if (data[i][j][2] > upperBound[2]) {
                    upperBound[2] = data[i][j][2];
                }
            }
        }

        PlotCanvas canvas = new PlotCanvas(lowerBound, upperBound);

        Surface surface = new Surface(data, palette);
        canvas.add(surface);

        return canvas;
    }
}
 

Ads help maintain this website.