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}