    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}