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}