    001/* ===========================================================
    002 * Orson Charts : a 3D chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C)opyright 2013-2016, by Object Refinery Limited.  All rights reserved.
    006 *
    007 * http://www.object-refinery.com/orsoncharts/index.html
    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 com.orsoncharts.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.Collections;
    045import java.util.List;
    046import com.orsoncharts.util.ArgChecks;
    047import com.orsoncharts.Chart3D;
    048
    049/**
    050 * Provides a default implementation of the {@link Drawable3D} interface.
    051 * This is not used directly in Orson Charts, since the {@link Chart3D} class
    052 * implements the {@link Drawable3D} interface itself.  However, it is used
    053 * in testing to ensure that the {@code com.orsoncharts.graphics3d}
    054 * package can function on a stand-alone basis.
    055 */
    056public class DefaultDrawable3D implements Drawable3D {
    057
    058    /**
    059     * The default projection distance.
    060     *
    061     * @since 1.2
    062     */
    063    public static final double DEFAULT_PROJ_DIST = 1500.0;
    064
    065    /** The viewing point. */
    066    private ViewPoint3D viewPoint;
    067
    068    /** The projection distance. */
    069    private double projDist;
    070
    071    /** The 3D world being drawn. */
    072    private World world;
    073
    074    private Offset2D offset;
    075
    076    /**
    077     * Creates a new instance to display the content of the specified
    078     * {@code world}.
    079     *
    080     * @param world  the world to view ({@code null} not permitted).
    081     */
    082    public DefaultDrawable3D(World world) {
    083        ArgChecks.nullNotPermitted(world, "world");
    084        this.viewPoint = new ViewPoint3D((float) (3 * Math.PI / 2.0),
    085                (float) Math.PI / 6, 40.0f, 0.0);
    086        this.projDist = DEFAULT_PROJ_DIST;
    087        this.world = world;
    088        this.offset = new Offset2D();
    089    }
    090
    091    /**
    092     * Returns the dimensions of the 3D object.
    093     *
    094     * @return The dimensions.
    095     */
    096    @Override
    097    public Dimension3D getDimensions() {
    098        return new Dimension3D(1.0, 1.0, 1.0);  // FIXME
    099    }
    100
    101    /**
    102     * Returns the view point.
    103     *
    104     * @return The view point (never {@code null}).
    105     */
    106    @Override
    107    public ViewPoint3D getViewPoint() {
    108        return this.viewPoint;
    109    }
    110
    111    /**
    112     * Sets the view point.
    113     *
    114     * @param viewPoint  the view point ({@code null} not permitted).
    115     */
    116    @Override
    117    public void setViewPoint(ViewPoint3D viewPoint) {
    118        ArgChecks.nullNotPermitted(viewPoint, "viewPoint");
    119        this.viewPoint = viewPoint;
    120    }
    121
    122    /**
    123     * Returns the projection distance.  The default value is
    124     * {@link #DEFAULT_PROJ_DIST}, higher numbers flatten out the perspective
    125     * and reduce distortion in the projected image.
    126     *
    127     * @return The projection distance.
    128     *
    129     * @since 1.2
    130     */
    131    @Override
    132    public double getProjDistance() {
    133        return this.projDist;
    134    }
    135
    136    /**
    137     * Sets the projection distance.
    138     *
    139     * @param dist  the distance.
    140     *
    141     * @since 1.2
    142     */
    143    @Override
    144    public void setProjDistance(double dist) {
    145        this.projDist = dist;
    146    }
    147
    148    @Override
    149    public Offset2D getTranslate2D() {
    150        return this.offset;
    151    }
    152
    153    @Override
    154    public void setTranslate2D(Offset2D offset) {
    155        ArgChecks.nullNotPermitted(offset, "offset");
    156        this.offset = offset;
    157    }
    158
    159    /**
    160     * Draws the current view to a {@code Graphics2D} instance.
    161     *
    162     * @param g2  the graphics target ({@code null} not permitted).
    163     * @param bounds  the bounds ({@code null} not permitted).
    164     *
    165     * @return The rendering state.
    166     */
    167    @Override
    168    public RenderingInfo draw(Graphics2D g2, Rectangle2D bounds) {
    169        ArgChecks.nullNotPermitted(g2, "g2");
    170        g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_ROUND,
    171                BasicStroke.JOIN_ROUND));
    172        g2.setPaint(Color.WHITE);
    173        g2.fill(bounds);
    174        AffineTransform saved = g2.getTransform();
    175        double dx = bounds.getWidth() / 2;
    176        double dy = bounds.getHeight() / 2;
    177        g2.translate(dx, dy);
    178        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
    179                RenderingHints.VALUE_ANTIALIAS_ON);
    180
    181        Point3D[] eyePts = this.world.calculateEyeCoordinates(this.viewPoint);
    182
    183        Point2D[] pts = this.world.calculateProjectedPoints(this.viewPoint,
    184                    this.projDist);
    185        List<Face> facesInPaintOrder = new ArrayList<Face>(
    186                this.world.getFaces());
    187
    188        // sort faces by z-order
    189        Collections.sort(facesInPaintOrder, 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        RenderingInfo info = new RenderingInfo(facesInPaintOrder, pts, dx, dy);
    222        return info;
    223    }
    224
    225}