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.Color; 036import java.awt.geom.Path2D; 037import java.awt.geom.Point2D; 038 039import org.jfree.chart3d.graphics3d.internal.TaggedFace; 040import org.jfree.chart3d.graphics3d.internal.Utils2D; 041import org.jfree.chart3d.internal.Args; 042 043/** 044 * Represents a face in one {@link Object3D}, defined in terms of vertex 045 * indices. It is expected (but not enforced) that all the vertices for 046 * the face lie within a single plane. The face will be visible from the 047 * "front" side only, which is a function of the order in which the vertices 048 * are specified. A special subclass, {@link DoubleSidedFace}, is visible 049 * from both front and back. 050 */ 051public class Face { 052 053 /** The object that the face belongs to. */ 054 private Object3D owner; 055 056 /** The offset into the global list of vertices. */ 057 private int offset; 058 059 /** 060 * The indices of the vertices representing this face. Normally a face 061 * should have at least three vertices (a triangle) but we allow a special 062 * case with just two vertices to represent a line. 063 */ 064 private int[] vertices; 065 066 /** 067 * Creates a new face with the specified vertices that is part of the 3D 068 * {@code owner} object. Most faces will have at least three vertices, 069 * but a special case with just two vertices (representing a line) is 070 * permitted. 071 * 072 * @param owner the object that owns the face ({@code null} not 073 * permitted). 074 * @param vertices the indices of the vertices (array length >= 2). 075 * 076 * @since 1.3 077 */ 078 public Face(Object3D owner, int[] vertices) { 079 if (vertices.length < 2) { 080 throw new IllegalArgumentException( 081 "Faces must have at least two vertices."); 082 } 083 Args.nullNotPermitted(owner, "owner"); 084 this.owner = owner; 085 this.vertices = vertices; 086 this.offset = 0; 087 } 088 089 /** 090 * Returns the object that this face belongs too (as passed to the 091 * constructor). 092 * 093 * @return The owner (never {@code null}). 094 * 095 * @since 1.3 096 */ 097 public Object3D getOwner() { 098 return this.owner; 099 } 100 101 /** 102 * Returns the offset to add to the vertex indices. 103 * 104 * @return The offset. 105 */ 106 public int getOffset() { 107 return this.offset; 108 } 109 110 /** 111 * Sets the offset to add to the vertex indices. 112 * 113 * @param offset the offset. 114 */ 115 public void setOffset(int offset) { 116 this.offset = offset; 117 } 118 119 /** 120 * Returns the number of vertices in this face. 121 * 122 * @return The number of vertices in this face. 123 */ 124 public int getVertexCount() { 125 return this.vertices.length; 126 } 127 128 /** 129 * Returns the index for the specified vertex. 130 * 131 * @param i the vertex index. 132 * 133 * @return The index. 134 */ 135 public int getVertexIndex(int i) { 136 return this.vertices[i] + this.offset; 137 } 138 139 /** 140 * A convenience method that looks up and returns the color for this face 141 * (obtained by querying the object that owns the face). The color is 142 * not stored as an attribute of the face, because typically an object 143 * has many faces that are all the same color. 144 * 145 * @return The color (never {@code null}). 146 */ 147 public Color getColor() { 148 return this.owner.getColor(this); 149 } 150 151 /** 152 * Returns {@code true} if an outline should be drawn for this face, 153 * and {@code false} otherwise. The value is obtained by querying 154 * the object that owns the face. 155 * 156 * @return A boolean. 157 */ 158 public boolean getOutline() { 159 return this.owner.getOutline(this); 160 } 161 162 /** 163 * Returns the tag for this face (always {@code null} for this class, 164 * subclasses may override). The {@link TaggedFace} class overrides 165 * this method. 166 * 167 * @return {@code null}. 168 * 169 * @since 1.3 170 */ 171 public String getTag() { 172 return null; 173 } 174 175 /** 176 * Calculates the normal vector for this face. 177 * 178 * @param points the vertices of the object that this face belongs to 179 * (these can be in world or eye coordinates). 180 * 181 * @return The normal vector. 182 */ 183 public double[] calculateNormal(Point3D[] points) { 184 int iA = this.vertices[0] + this.offset; 185 int iB = this.vertices[1] + this.offset; 186 int iC = this.vertices[2] + this.offset; 187 double aX = points[iA].x; 188 double aY = points[iA].y; 189 double aZ = points[iA].z; 190 double bX = points[iB].x; 191 double bY = points[iB].y; 192 double bZ = points[iB].z; 193 double cX = points[iC].x; 194 double cY = points[iC].y; 195 double cZ = points[iC].z; 196 double u1 = bX - aX, u2 = bY - aY, u3 = bZ - aZ; 197 double v1 = cX - aX, v2 = cY - aY, v3 = cZ - aZ; 198 double a = u2 * v3 - u3 * v2, 199 b = u3 * v1 - u1 * v3, 200 c = u1 * v2 - u2 * v1, 201 len = Math.sqrt(a * a + b * b + c * c); 202 a /= len; b /= len; c /= len; 203 return new double[] {a, b, c}; 204 } 205 206 /** 207 * Returns the average z-value. 208 * 209 * @param points the points. 210 * 211 * @return The average z-value. 212 */ 213 public float calculateAverageZValue(Point3D[] points) { 214 float total = 0.0f; 215 for (int i = 0; i < this.vertices.length; i++) { 216 total = total + (float) points[this.vertices[i] + this.offset].z; 217 } 218 return total / this.vertices.length; 219 } 220 221 /** 222 * Returns {@code true} if this face is front facing, and 223 * {@code false} otherwise. 224 * 225 * @param projPts the projection points. 226 * 227 * @return A boolean. 228 */ 229 public boolean isFrontFacing(Point2D[] projPts) { 230 return Utils2D.area2(projPts[getVertexIndex(0)], 231 projPts[getVertexIndex(1)], projPts[getVertexIndex(2)]) > 0; 232 } 233 234 /** 235 * Creates and returns a path for the outline of this face. 236 * 237 * @param pts the projected points for the world ({@code null} not 238 * permitted). 239 * 240 * @return A path. 241 * 242 * @since 1.3 243 */ 244 public Path2D createPath(Point2D[] pts) { 245 Path2D path = new Path2D.Float(); 246 for (int v = 0; v < getVertexCount(); v++) { 247 Point2D pt = pts[getVertexIndex(v)]; 248 if (v == 0) { 249 path.moveTo(pt.getX(), pt.getY()); 250 } else { 251 path.lineTo(pt.getX(), pt.getY()); 252 } 253 } 254 path.closePath(); 255 return path; 256 } 257 258 /** 259 * Returns a string representation of this instance, primarily for 260 * debugging purposes. 261 * 262 * @return A string. 263 */ 264 @Override 265 public String toString() { 266 String result = "["; 267 for (int i = 0; i < this.vertices.length; i++) { 268 result = result + this.vertices[i]; 269 if (i < this.vertices.length - 1) { 270 result = result + ", "; 271 } 272 } 273 return result + "]"; 274 } 275}