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 &gt;= 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}