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