/* =========================================================== * Orson Charts : a 3D chart library for the Java(tm) platform * =========================================================== * * (C)opyright 2013-2016, by Object Refinery Limited. All rights reserved. * * http://www.object-refinery.com/orsoncharts/index.html * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners.] * * If you do not wish to be bound by the terms of the GPL, an alternative * commercial license can be purchased. For details, please see visit the * Orson Charts home page: * * http://www.object-refinery.com/orsoncharts/index.html * */ package com.orsoncharts.graphics3d; import java.awt.Color; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import com.orsoncharts.util.ArgChecks; /** * Represents a face in one {@link Object3D}, defined in terms of vertex * indices. It is expected (but not enforced) that all the vertices for * the face lie within a single plane. The face will be visible from the * "front" side only, which is a function of the order in which the vertices * are specified. A special subclass, {@link DoubleSidedFace}, is visible * from both front and back. */ public class Face { /** The object that the face belongs to. */ private Object3D owner; /** The offset into the global list of vertices. */ private int offset; /** * The indices of the vertices representing this face. Normally a face * should have at least three vertices (a triangle) but we allow a special * case with just two vertices to represent a line. */ private int[] vertices; /** * Creates a new face with the specified vertices that is part of the 3D * {@code owner} object. Most faces will have at least three vertices, * but a special case with just two vertices (representing a line) is * permitted. * * @param owner the object that owns the face ({@code null} not * permitted). * @param vertices the indices of the vertices (array length >= 2). * * @since 1.3 */ public Face(Object3D owner, int[] vertices) { if (vertices.length < 2) { throw new IllegalArgumentException( "Faces must have at least two vertices."); } ArgChecks.nullNotPermitted(owner, "owner"); this.owner = owner; this.vertices = vertices; this.offset = 0; } /** * Returns the object that this face belongs too (as passed to the * constructor). * * @return The owner (never {@code null}). * * @since 1.3 */ public Object3D getOwner() { return this.owner; } /** * Returns the offset to add to the vertex indices. * * @return The offset. */ public int getOffset() { return this.offset; } /** * Sets the offset to add to the vertex indices. * * @param offset the offset. */ public void setOffset(int offset) { this.offset = offset; } /** * Returns the number of vertices in this face. * * @return The number of vertices in this face. */ public int getVertexCount() { return this.vertices.length; } /** * Returns the index for the specified vertex. * * @param i the vertex index. * * @return The index. */ public int getVertexIndex(int i) { return this.vertices[i] + this.offset; } /** * A convenience method that looks up and returns the color for this face * (obtained by querying the object that owns the face). The color is * not stored as an attribute of the face, because typically an object * has many faces that are all the same color. * * @return The color (never {@code null}). */ public Color getColor() { return this.owner.getColor(this); } /** * Returns {@code true} if an outline should be drawn for this face, * and {@code false} otherwise. The value is obtained by querying * the object that owns the face. * * @return A boolean. */ public boolean getOutline() { return this.owner.getOutline(this); } /** * Returns the tag for this face (always {@code null} for this class, * subclasses may override). The {@link TaggedFace} class overrides * this method. * * @return {@code null}. * * @since 1.3 */ public String getTag() { return null; } /** * Calculates the normal vector for this face. * * @param points the vertices of the object that this face belongs to * (these can be in world or eye coordinates). * * @return The normal vector. */ public double[] calculateNormal(Point3D[] points) { int iA = this.vertices[0] + this.offset; int iB = this.vertices[1] + this.offset; int iC = this.vertices[2] + this.offset; double aX = points[iA].x; double aY = points[iA].y; double aZ = points[iA].z; double bX = points[iB].x; double bY = points[iB].y; double bZ = points[iB].z; double cX = points[iC].x; double cY = points[iC].y; double cZ = points[iC].z; double u1 = bX - aX, u2 = bY - aY, u3 = bZ - aZ; double v1 = cX - aX, v2 = cY - aY, v3 = cZ - aZ; double a = u2 * v3 - u3 * v2, b = u3 * v1 - u1 * v3, c = u1 * v2 - u2 * v1, len = Math.sqrt(a * a + b * b + c * c); a /= len; b /= len; c /= len; return new double[] {a, b, c}; } /** * Returns the average z-value. * * @param points the points. * * @return The average z-value. */ public float calculateAverageZValue(Point3D[] points) { float total = 0.0f; for (int i = 0; i < this.vertices.length; i++) { total = total + (float) points[this.vertices[i] + this.offset].z; } return total / this.vertices.length; } /** * Returns {@code true} if this face is front facing, and * {@code false} otherwise. * * @param projPts the projection points. * * @return A boolean. */ public boolean isFrontFacing(Point2D[] projPts) { return Utils2D.area2(projPts[getVertexIndex(0)], projPts[getVertexIndex(1)], projPts[getVertexIndex(2)]) > 0; } /** * Creates and returns a path for the outline of this face. * * @param pts the projected points for the world ({@code null} not * permitted). * * @return A path. * * @since 1.3 */ public Path2D createPath(Point2D[] pts) { Path2D path = new Path2D.Float(); for (int v = 0; v < getVertexCount(); v++) { Point2D pt = pts[getVertexIndex(v)]; if (v == 0) { path.moveTo(pt.getX(), pt.getY()); } else { path.lineTo(pt.getX(), pt.getY()); } } path.closePath(); return path; } /** * Returns a string representation of this instance, primarily for * debugging purposes. * * @return A string. */ @Override public String toString() { String result = "["; for (int i = 0; i < this.vertices.length; i++) { result = result + this.vertices[i]; if (i < this.vertices.length - 1) { result = result + ", "; } } return result + "]"; } }