    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}