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