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}