    001/* ===========================================================
    002 * Orson Charts : a 3D chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C)opyright 2013-2022, by David Gilbert.  All rights reserved.
    006 *
    007 * https://github.com/jfree/orson-charts
    008 *
    009 * This program is free software: you can redistribute it and/or modify
    010 * it under the terms of the GNU General Public License as published by
    011 * the Free Software Foundation, either version 3 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This program is distributed in the hope that it will be useful,
    015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
    016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    017 * GNU General Public License for more details.
    018 *
    019 * You should have received a copy of the GNU General Public License
    020 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    021 *
    022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    023 * Other names may be trademarks of their respective owners.]
    024 *
    025 * If you do not wish to be bound by the terms of the GPL, an alternative
    026 * commercial license can be purchased.  For details, please see visit the
    027 * Orson Charts home page:
    028 *
    029 * http://www.object-refinery.com/orsoncharts/index.html
    030 *
    031 */
    032
    033package org.jfree.chart3d.graphics3d;
    034
    035import java.awt.BasicStroke;
    036import java.awt.Color;
    037import java.awt.Graphics2D;
    038import java.awt.RenderingHints;
    039import java.awt.geom.Rectangle2D;
    040import java.awt.geom.AffineTransform;
    041import java.awt.geom.GeneralPath;
    042import java.awt.geom.Point2D;
    043import java.util.ArrayList;
    044import java.util.List;
    045import org.jfree.chart3d.internal.Args;
    046import org.jfree.chart3d.Chart3D;
    047import org.jfree.chart3d.graphics3d.internal.Utils2D;
    048import org.jfree.chart3d.graphics3d.internal.ZOrderComparator;
    049
    050/**
    051 * Provides a default implementation of the {@link Drawable3D} interface.
    052 * This is not used directly in Orson Charts, since the {@link Chart3D} class
    053 * implements the {@link Drawable3D} interface itself.  However, it is used
    054 * in testing to ensure that the {@code org.jfree.chart3d.graphics3d}
    055 * package can function on a stand-alone basis.
    056 */
    057public class DefaultDrawable3D implements Drawable3D {
    058
    059    /**
    060     * The default projection distance.
    061     *
    062     * @since 1.2
    063     */
    064    public static final double DEFAULT_PROJ_DIST = 1500.0;
    065
    066    /** The viewing point. */
    067    private ViewPoint3D viewPoint;
    068
    069    /** The projection distance. */
    070    private double projDist;
    071
    072    /** The 3D world being drawn. */
    073    private World world;
    074
    075    private Offset2D offset;
    076
    077    /**
    078     * Creates a new instance to display the content of the specified
    079     * {@code world}.
    080     *
    081     * @param world  the world to view ({@code null} not permitted).
    082     */
    083    public DefaultDrawable3D(World world) {
    084        Args.nullNotPermitted(world, "world");
    085        this.viewPoint = new ViewPoint3D((float) (3 * Math.PI / 2.0),
    086                (float) Math.PI / 6, 40.0f, 0.0);
    087        this.projDist = DEFAULT_PROJ_DIST;
    088        this.world = world;
    089        this.offset = new Offset2D();
    090    }
    091
    092    /**
    093     * Returns the dimensions of the 3D object.
    094     *
    095     * @return The dimensions.
    096     */
    097    @Override
    098    public Dimension3D getDimensions() {
    099        return new Dimension3D(1.0, 1.0, 1.0);  // FIXME
    100    }
    101
    102    /**
    103     * Returns the view point.
    104     *
    105     * @return The view point (never {@code null}).
    106     */
    107    @Override
    108    public ViewPoint3D getViewPoint() {
    109        return this.viewPoint;
    110    }
    111
    112    /**
    113     * Sets the view point.
    114     *
    115     * @param viewPoint  the view point ({@code null} not permitted).
    116     */
    117    @Override
    118    public void setViewPoint(ViewPoint3D viewPoint) {
    119        Args.nullNotPermitted(viewPoint, "viewPoint");
    120        this.viewPoint = viewPoint;
    121    }
    122
    123    /**
    124     * Returns the projection distance.  The default value is
    125     * {@link #DEFAULT_PROJ_DIST}, higher numbers flatten out the perspective
    126     * and reduce distortion in the projected image.
    127     *
    128     * @return The projection distance.
    129     *
    130     * @since 1.2
    131     */
    132    @Override
    133    public double getProjDistance() {
    134        return this.projDist;
    135    }
    136
    137    /**
    138     * Sets the projection distance.
    139     *
    140     * @param dist  the distance.
    141     *
    142     * @since 1.2
    143     */
    144    @Override
    145    public void setProjDistance(double dist) {
    146        this.projDist = dist;
    147    }
    148
    149    @Override
    150    public Offset2D getTranslate2D() {
    151        return this.offset;
    152    }
    153
    154    @Override
    155    public void setTranslate2D(Offset2D offset) {
    156        Args.nullNotPermitted(offset, "offset");
    157        this.offset = offset;
    158    }
    159
    160    /**
    161     * Draws the current view to a {@code Graphics2D} instance.
    162     *
    163     * @param g2  the graphics target ({@code null} not permitted).
    164     * @param bounds  the bounds ({@code null} not permitted).
    165     *
    166     * @return The rendering state.
    167     */
    168    @Override
    169    public RenderingInfo draw(Graphics2D g2, Rectangle2D bounds) {
    170        Args.nullNotPermitted(g2, "g2");
    171        g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_ROUND,
    172                BasicStroke.JOIN_ROUND));
    173        g2.setPaint(Color.WHITE);
    174        g2.fill(bounds);
    175        AffineTransform saved = g2.getTransform();
    176        double dx = bounds.getWidth() / 2;
    177        double dy = bounds.getHeight() / 2;
    178        g2.translate(dx, dy);
    179        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
    180                RenderingHints.VALUE_ANTIALIAS_ON);
    181
    182        Point3D[] eyePts = this.world.calculateEyeCoordinates(this.viewPoint);
    183
    184        Point2D[] pts = this.world.calculateProjectedPoints(this.viewPoint,
    185                    this.projDist);
    186        List<Face> facesInPaintOrder = new ArrayList<>(this.world.getFaces());
    187
    188        // sort faces by z-order
    189        facesInPaintOrder.sort(new ZOrderComparator(eyePts));
    190
    191        for (Face f : facesInPaintOrder) {
    192            double[] plane = f.calculateNormal(eyePts);
    193            double inprod = plane[0] * this.world.getSunX() + plane[1]
    194                    * this.world.getSunY() + plane[2] * this.world.getSunZ();
    195            double shade = (inprod + 1) / 2.0;
    196            if (Utils2D.area2(pts[f.getVertexIndex(0)],
    197                    pts[f.getVertexIndex(1)], pts[f.getVertexIndex(2)]) > 0) {
    198                Color c = f.getColor();
    199                if (c != null) {
    200                    GeneralPath p = new GeneralPath();
    201                    for (int v = 0; v < f.getVertexCount(); v++) {
    202                        if (v == 0) {
    203                            p.moveTo(pts[f.getVertexIndex(v)].getX(),
    204                                    pts[f.getVertexIndex(v)].getY());
    205                        }
    206                        else {
    207                            p.lineTo(pts[f.getVertexIndex(v)].getX(),
    208                                    pts[f.getVertexIndex(v)].getY());
    209                        }
    210                    }
    211                    p.closePath();
    212                    g2.setPaint(new Color((int) (c.getRed() * shade),
    213                        (int) (c.getGreen() * shade),
    214                        (int) (c.getBlue() * shade), c.getAlpha()));
    215                    g2.fill(p);
    216                    g2.draw(p);
    217                }
    218            }
    219        }
    220        g2.setTransform(saved);
    221        return new RenderingInfo(facesInPaintOrder, pts, dx, dy);
    222    }
    223
    224}