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}