    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.Font;
    037import java.awt.geom.Point2D;
    038import java.util.ArrayList;
    039import java.util.HashMap;
    040import java.util.List;
    041import java.util.Map;
    042import org.jfree.chart3d.graphics3d.internal.TaggedFace;
    043import org.jfree.chart3d.internal.Args;
    044
    045/**
    046 * An object defined in 3D space by (a) a list of coordinates, and (b) a list
    047 * of faces.  This class has methods to calculate projected points in 2D when
    048 * a {@link ViewPoint3D} is provided.
    049 * <br><br>
    050 * This class also contains a collection of static methods for constructing
    051 * common 3D objects.
    052 */
    053public class Object3D {
    054
    055    /**
    056     * The key for storing the object class as an optional property for this
    057     * object.
    058     *
    059     * @since 1.4
    060     */
    061    public static final String CLASS_KEY = "class";
    062
    063    /**
    064     * The key for storing item keys as property values.
    065     *
    066     * @since 1.3
    067     */
    068    public static final String ITEM_KEY = "key";
    069
    070    /**
    071     * A prefix used for setting color properties for an object.
    072     *
    073     * @since 1.3
    074     */
    075    public static final String COLOR_PREFIX = "color/";
    076
    077    /** World coordinates. */
    078    private List<Point3D> vertices;
    079
    080    /** Faces for the object, specified by indices to the world coords. */
    081    private List<Face> faces;
    082
    083    /** The primary color for the object. */
    084    private Color color;
    085
    086    /**
    087     * A flag that indicates whether or not faces for this object have their
    088     * outlines drawn (that is, the shape is filled then drawn versus just
    089     * filled only).
    090     */
    091    private boolean outline;
    092
    093    /**
    094     * A map containing properties for the object.  If there are no properties
    095     * defined, then we leave this as {@code null} as an empty map would
    096     * consume memory unnecessarily.
    097     */
    098    private Map<String, Object> properties;
    099
    100    /**
    101     * Creates a new object, initially with no vertices or faces.
    102     *
    103     * @param color  the default face color ({@code null} not permitted).
    104     *
    105     * @since 1.3
    106     */
    107    public Object3D(Color color) {
    108        this(color, false);
    109    }
    110
    111    /**
    112     * Creates a new object, initially with no vertices or faces.
    113     *
    114     * @param color  the default face color ({@code null} not permitted).
    115     * @param outline  the default flag that determines whether face outlines
    116     *     are drawn.
    117     *
    118     * @since 1.3
    119     */
    120    public Object3D(Color color, boolean outline) {
    121        Args.nullNotPermitted(color, "color");
    122        this.color = color;
    123        this.outline = outline;
    124        this.vertices = new java.util.ArrayList<>();
    125        this.faces = new java.util.ArrayList<>();
    126    }
    127
    128    /**
    129     * Returns the default face color as specified in the constructor.
    130     *
    131     * @return The color (never {@code null}).
    132     *
    133     * @since 1.3
    134     */
    135    public Color getColor() {
    136        return this.color;
    137    }
    138
    139    /**
    140     * Returns the outline flag.
    141     *
    142     * @return The outline flag.
    143     *
    144     * @since 1.3
    145     */
    146    public boolean getOutline() {
    147        return this.outline;
    148    }
    149
    150    /**
    151     * Sets the outline flag.  This determines the default setting for whether
    152     * or not the faces of this object have their outlines drawn when rendered.
    153     *
    154     * @param outline  the new flag value.
    155     *
    156     * @since 1.3
    157     */
    158    public void setOutline(boolean outline) {
    159        this.outline = outline;
    160    }
    161
    162    /**
    163     * Returns the value of the property with the specified key, or
    164     * {@code null} if there is no property defined for that key.
    165     *
    166     * @param key  the property key ({@code null} not permitted).
    167     *
    168     * @return The value (possibly {@code null}).
    169     *
    170     * @since 1.3
    171     */
    172    public Object getProperty(String key) {
    173        Args.nullNotPermitted(key, "key");
    174        if (this.properties == null) {
    175            return null;
    176        } else {
    177            return this.properties.get(key);
    178        }
    179    }
    180
    181    /**
    182     * Sets the value of a property, overwriting any existing value.  One
    183     * application for this is storing item key references to link a 3D object
    184     * back to the data item that it represents (the key for this is
    185     * {@link Object3D#ITEM_KEY}).
    186     *
    187     * @param key  the key ({@code null} not permitted).
    188     * @param value  the value ({@code null} permitted).
    189     *
    190     * @since 1.3
    191     */
    192    public void setProperty(String key, Object value) {
    193        Args.nullNotPermitted(key, "key");
    194        if (this.properties == null) {
    195            this.properties = new HashMap<>();
    196        }
    197        this.properties.put(key, value);
    198    }
    199
    200    /**
    201     * Returns the color for a specific face.  If the face has a tag, then
    202     * this method will look for a property with the key COLOR_PREFIX + tag
    203     * and return that color, otherwise it returns the default color for the
    204     * object.
    205     *
    206     * @param face  the face ({@code null} not permitted).
    207     *
    208     * @return The color for the specified face (never {@code null}).
    209     *
    210     * @since 1.3
    211     */
    212    public Color getColor(Face face) {
    213        if (face.getTag() != null) {
    214            // see if there is a custom color defined for the tag
    215            Object obj = getProperty(COLOR_PREFIX + face.getTag());
    216            if (obj != null) {
    217                return (Color) obj;
    218            }
    219        }
    220        return this.color;
    221    }
    222
    223    /**
    224     * Returns {@code true} if an outline should be drawn for the
    225     * specified face, and {@code false} otherwise.
    226     *
    227     * @param face  the face ({@code null} not permitted).
    228     *
    229     * @return A boolean.
    230     *
    231     * @since 1.3
    232     */
    233    public boolean getOutline(Face face) {
    234        return this.outline;
    235    }
    236
    237    /**
    238     * Returns the number of vertices for this object.
    239     *
    240     * @return The number of vertices.
    241     */
    242    public int getVertexCount() {
    243        return this.vertices.size();
    244    }
    245
    246    /**
    247     * Adds a new object vertex with the specified coordinates.
    248     *
    249     * @param x  the x-coordinate.
    250     * @param y  the y-coordinate.
    251     * @param z  the z-coordinate.
    252     */
    253    public void addVertex(double x, double y, double z) {
    254        addVertex(new Point3D(x, y, z));
    255    }
    256
    257    /**
    258     * Adds a new object vertex.
    259     *
    260     * @param vertex  the vertex ({@code null} not permitted).
    261     */
    262    public void addVertex(Point3D vertex) {
    263        Args.nullNotPermitted(vertex, "vertex");
    264        this.vertices.add(vertex);
    265    }
    266
    267    /**
    268     * Returns the number of faces.
    269     *
    270     * @return The number of faces.
    271     */
    272    public int getFaceCount() {
    273        return this.faces.size();
    274    }
    275
    276    /**
    277     * Adds a face for the given vertices (specified by index value).
    278     *
    279     * @param vertices  the vertices (all should lie in a plane).
    280     *
    281     * @since 1.3
    282     */
    283    public void addFace(int[] vertices) {
    284        // defer the arg checks...
    285        addFace(new Face(this, vertices));
    286    }
    287
    288    /**
    289     * Adds a tagged face for the given vertices (specified by index value).
    290     *
    291     * @param vertices  the vertices (all should lie in a plane).
    292     * @param tag  the tag ({@code null} not permitted).
    293     *
    294     * @since 1.3
    295     */
    296    public void addFace(int[] vertices, String tag) {
    297        addFace(new TaggedFace(this, vertices, tag));
    298    }
    299
    300    /**
    301     * Adds a double-sided face for the given vertices (specified by index
    302     * value) and color.
    303     *
    304     * @param vertices  the vertices (all should lie in a plane).
    305     *
    306     * @since 1.3
    307     */
    308    public void addDoubleSidedFace(int[] vertices) {
    309        addFace(new DoubleSidedFace(this, vertices));
    310    }
    311
    312    /**
    313     * Adds a face for this object.
    314     *
    315     * @param face  the face ({@code null} not permitted).
    316     */
    317    public void addFace(Face face) {
    318        Args.nullNotPermitted(face, "face");
    319        this.faces.add(face);
    320    }
    321
    322    /**
    323     * Returns the faces for this object.  Note that the list returned is a
    324     * direct reference to the internal storage for this {@code Object3D}
    325     * instance, so callers should take care not to modify this list
    326     * unintentionally.
    327     *
    328     * @return The faces.
    329     */
    330    public List<Face> getFaces() {
    331        return this.faces;
    332    }
    333
    334    /**
    335     * Calculates the projected points for the object's vertices, for the
    336     * given viewpoint.
    337     *
    338     * @param viewPoint  the view point ({@code null} not permitted).
    339     * @param d  the projection distance.
    340     *
    341     * @return The projected points.
    342     */
    343    public Point2D[] calculateProjectedPoints(ViewPoint3D viewPoint, double d) {
    344        Args.nullNotPermitted(viewPoint, "viewPoint");
    345        Point2D[] result = new Point2D[this.vertices.size()];
    346        int vertexCount = this.vertices.size();
    347        for (int i = 0; i < vertexCount; i++) {
    348            Point3D p = this.vertices.get(i);
    349            result[i] = viewPoint.worldToScreen(p, d);
    350        }
    351        return result;
    352    }
    353
    354    /**
    355     * Returns the eye coordinates of the object's vertices.
    356     *
    357     * @param viewPoint  the view point ({@code null} not permitted).
    358     *
    359     * @return The eye coordinates.
    360     */
    361    public Point3D[] calculateEyeCoordinates(ViewPoint3D viewPoint) {
    362        Args.nullNotPermitted(viewPoint, "viewPoint");
    363        Point3D[] result = new Point3D[this.vertices.size()];
    364        int i = 0;
    365        for (Point3D vertex : this.vertices) {
    366            result[i] = viewPoint.worldToEye(vertex);
    367            i++;
    368        }
    369        return result;
    370    }
    371
    372    /**
    373     * Creates a square flat surface in the x-z plane (constant y) with a
    374     * single face.
    375     *
    376     * @param size  the sheet size.
    377     * @param x  the x-coordinate for the center of the square.
    378     * @param y  the y-coordinate.
    379     * @param z  the z-coordinate for the center of the square.
    380     * @param color  the color ({@code null} not permitted).
    381     * @param invert  invert the order of the face
    382     *
    383     * @return  The sheet.
    384     */
    385    public static Object3D createYSheet(double size, double x, double y,
    386            double z, Color color, boolean invert) {
    387        Args.nullNotPermitted(color, "color");
    388        Object3D sheet = new Object3D(color);
    389        double delta = size / 2.0;
    390        sheet.addVertex(new Point3D(x + delta, y, z - delta));
    391        sheet.addVertex(new Point3D(x + delta, y, z + delta));
    392        sheet.addVertex(new Point3D(x - delta, y, z + delta));
    393        sheet.addVertex(new Point3D(x - delta, y, z - delta));
    394        if (invert) {
    395            sheet.addFace(new Face(sheet, new int[] {3, 2, 1, 0}));
    396        } else {
    397            sheet.addFace(new Face(sheet, new int[] {0, 1, 2, 3}));
    398        }
    399        return sheet;
    400    }
    401
    402    /**
    403     * Creates a square flat surface in the x-y plane (constant z).
    404     *
    405     * @param size  the sheet size.
    406     * @param x  the x-coordinate of a point on the surface.
    407     * @param y  the y-coordinate of a point on the surface.
    408     * @param z  the z-coordinate of a point on the surface.
    409     * @param color  the color.
    410     *
    411     * @return The sheet.
    412     */
    413    public static Object3D createZSheet(double size, double x, double y,
    414            double z, Color color) {
    415        Object3D sheet = new Object3D(color);
    416        double delta = size / 2.0;
    417        sheet.addVertex(new Point3D(x + delta, y - delta, z));
    418        sheet.addVertex(new Point3D(x + delta, y + delta, z));
    419        sheet.addVertex(new Point3D(x - delta, y + delta, z));
    420        sheet.addVertex(new Point3D(x - delta, y - delta, z));
    421        sheet.addFace(new Face(sheet, new int[] {0, 1, 2, 3}));
    422        return sheet;
    423    }
    424
    425    /**
    426     * Creates a cube centered on {@code (x, y, z)} with the specified
    427     * {@code size}.
    428     *
    429     * @param size  the size.
    430     * @param x  the x-offset.
    431     * @param y  the y-offset.
    432     * @param z  the z-offset.
    433     * @param color  the color ({@code null} not permitted).
    434     *
    435     * @return The cube (never {@code null}).
    436     */
    437    public static Object3D createCube(double size, double x,
    438            double y, double z, Color color) {
    439        return createBox(x, size, y, size, z, size, color);
    440    }
    441
    442    /**
    443     * Creates a box centered on {@code (x, y, z)} with the specified
    444     * dimensions.
    445     *
    446     * @param x  the x-coordinate.
    447     * @param xdim  the length of the box in the x-dimension.
    448     * @param y  the y-coordinate.
    449     * @param ydim  the length of the box in the y-dimension.
    450     * @param z  the z-coordinate.
    451     * @param zdim  the length of the box in the y-dimension.
    452     * @param color  the color ({@code null} not permitted).
    453     *
    454     * @return The box (never {@code null}).
    455     *
    456     * @see #createCube(double, double, double, double, java.awt.Color)
    457     */
    458    public static Object3D createBox(double x, double xdim,
    459            double y, double ydim, double z, double zdim,
    460            Color color) {
    461        Args.nullNotPermitted(color, "color");
    462        Object3D box = new Object3D(color);
    463        double xdelta = xdim / 2.0;
    464        double ydelta = ydim / 2.0;
    465        double zdelta = zdim / 2.0;
    466        box.addVertex(new Point3D(x - xdelta, y - ydelta, z - zdelta));
    467        box.addVertex(new Point3D(x + xdelta, y - ydelta, z - zdelta));
    468        box.addVertex(new Point3D(x + xdelta, y - ydelta, z + zdelta));
    469        box.addVertex(new Point3D(x - xdelta, y - ydelta, z + zdelta));
    470        box.addVertex(new Point3D(x - xdelta, y + ydelta, z - zdelta));
    471        box.addVertex(new Point3D(x + xdelta, y + ydelta, z - zdelta));
    472        box.addVertex(new Point3D(x + xdelta, y + ydelta, z + zdelta));
    473        box.addVertex(new Point3D(x - xdelta, y + ydelta, z + zdelta));
    474        box.addFace(new Face(box, new int[] {4, 5, 1, 0}));
    475        box.addFace(new Face(box, new int[] {5, 6, 2, 1}));
    476        box.addFace(new Face(box, new int[] {6, 7, 3, 2}));
    477        box.addFace(new Face(box, new int[] {3, 7, 4, 0}));
    478        box.addFace(new Face(box, new int[] {7, 6, 5, 4}));
    479        box.addFace(new Face(box, new int[] {0, 1, 2, 3}));
    480        return box;
    481    }
    482
    483    /**
    484     * Creates a tetrahedron.
    485     *
    486     * @param size  the size.
    487     * @param xOffset  the x-offset.
    488     * @param yOffset  the y-offset.
    489     * @param zOffset  the z-offset.
    490     * @param color  the color ({@code null} not permitted).
    491     *
    492     * @return A tetrahedron.
    493     */
    494    public static Object3D createTetrahedron(double size, double xOffset,
    495            double yOffset, double zOffset, Color color) {
    496        Args.nullNotPermitted(color, "color");
    497        Object3D tetra = new Object3D(color);
    498        tetra.addVertex(new Point3D(size + xOffset, -size + yOffset,
    499                -size + zOffset));
    500        tetra.addVertex(new Point3D(-size + xOffset, size + yOffset,
    501                -size + zOffset));
    502        tetra.addVertex(new Point3D(size + xOffset, size + yOffset,
    503                size + zOffset));
    504        tetra.addVertex(new Point3D(-size + xOffset, -size + yOffset,
    505                size + zOffset));
    506        tetra.addFace(new Face(tetra, new int[] {0, 1, 2}));
    507        tetra.addFace(new Face(tetra, new int[] {1, 3, 2}));
    508        tetra.addFace(new Face(tetra, new int[] {0, 3, 1}));
    509        tetra.addFace(new Face(tetra, new int[] {0, 2, 3}));
    510        return tetra;
    511    }
    512
    513    /**
    514     * Creates an octahedron.
    515     *
    516     * @param size  the size.
    517     * @param xOffset  the x-offset.
    518     * @param yOffset  the y-offset.
    519     * @param zOffset  the z-offset.
    520     * @param color  the color ({@code null} not permitted).
    521     *
    522     * @return An octahedron.
    523     */
    524    public static Object3D createOctahedron(double size, double xOffset,
    525            double yOffset, double zOffset, Color color) {
    526        Args.nullNotPermitted(color, "color");
    527        Object3D octa = new Object3D(color);
    528        octa.addVertex(new Point3D(size + xOffset, 0 + yOffset, 0 + zOffset));
    529        octa.addVertex(new Point3D(0 + xOffset, size + yOffset, 0 + zOffset));
    530        octa.addVertex(new Point3D(-size + xOffset, 0 + yOffset, 0 + zOffset));
    531        octa.addVertex(new Point3D(0 + xOffset, -size + yOffset, 0 + zOffset));
    532        octa.addVertex(new Point3D(0 + xOffset, 0 + yOffset, -size + zOffset));
    533        octa.addVertex(new Point3D(0 + xOffset, 0 + yOffset, size + zOffset));
    534
    535        octa.addFace(new Face(octa, new int[] {0, 1, 5}));
    536        octa.addFace(new Face(octa, new int[] {1, 2, 5}));
    537        octa.addFace(new Face(octa, new int[] {2, 3, 5}));
    538        octa.addFace(new Face(octa, new int[] {3, 0, 5}));
    539        octa.addFace(new Face(octa, new int[] {1, 0, 4}));
    540        octa.addFace(new Face(octa, new int[] {2, 1, 4}));
    541        octa.addFace(new Face(octa, new int[] {3, 2, 4}));
    542        octa.addFace(new Face(octa, new int[] {0, 3, 4}));
    543        return octa;
    544    }
    545
    546    /**
    547     * Creates an approximation of a sphere.
    548     *
    549     * @param radius  the radius of the sphere (in world units).
    550     * @param n  the number of layers.
    551     * @param x  the x-coordinate of the center of the sphere.
    552     * @param y  the y-coordinate of the center of the sphere.
    553     * @param z  the z-coordinate of the center of the sphere.
    554     * @param extColor  the exterior color ({@code null} not permitted).
    555     * @param intColor  the interior color ({@code null} not permitted).
    556     *
    557     * @return A sphere.
    558     */
    559    public static Object3D createSphere(double radius, int n,
    560            double x, double y, double z, Color extColor, Color intColor) {
    561        Object3D sphere = new Object3D(extColor);
    562        sphere.setProperty(COLOR_PREFIX + "interior", intColor);
    563        double theta = Math.PI / n;
    564        Point3D[] prevLayer = new Point3D[n * 2 + 1];
    565        for (int i = 0; i <= n * 2; i++) {
    566            prevLayer[i] = new Point3D(x, y + radius, z);
    567            if (i != n * 2) {
    568                sphere.addVertex(prevLayer[i]);
    569            }
    570        }
    571
    572        for (int layer = 1; layer < n; layer++) {
    573            Point3D[] currLayer = new Point3D[n * 2 + 1];
    574            for (int i = 0; i <= n * 2; i++) {
    575                double xx = radius * Math.cos(i * theta)
    576                        * Math.sin(layer * theta);
    577                double yy = radius * Math.cos(layer * theta);
    578                double zz = radius * Math.sin(i * theta)
    579                        * Math.sin(layer * theta);
    580                currLayer[i] = new Point3D(x + xx, y + yy, z + zz);
    581                if (i != n * 2) {
    582                    sphere.addVertex(currLayer[i]);
    583                }
    584                if (i > 0 && layer > 1) {
    585                    if (i != n * 2) {
    586                        Face f = new Face(sphere, new int[] {
    587                            (layer - 1) * n * 2 + i - 1,
    588                            (layer - 1) * n * 2 + i, layer * n * 2 + i,
    589                            layer * n * 2 + i - 1});
    590                        sphere.addFace(f);
    591                        f = new TaggedFace(sphere, new int[] {
    592                            layer * n * 2 + i - 1, layer * n * 2 + i,
    593                            (layer - 1) * n * 2 + i,
    594                            (layer - 1) * n * 2 + i - 1}, "interior");
    595                        sphere.addFace(f);
    596                    } else {
    597                        sphere.addFace(new Face(sphere, new int[] {
    598                            (layer - 1) * n * 2 + i - 1, (layer - 1) * n * 2,
    599                            layer * n * 2, layer * n * 2 + i - 1}));
    600                        sphere.addFace(new TaggedFace(sphere, new int[] {
    601                            layer * n * 2 + i - 1, layer * n * 2,
    602                            (layer - 1) * n * 2, (layer - 1) * n * 2 + i - 1},
    603                            "interior"));
    604                    }
    605                }
    606            }
    607        }
    608        return sphere;
    609    }
    610
    611    /**
    612     * Creates a pie segment with the specified attributes.
    613     *
    614     * @param radius  the radius.
    615     * @param explodeRadius  the explode radius (0.0 if not exploded).
    616     * @param base  the base.
    617     * @param height  the height.
    618     * @param angle1  the start angle (radians).
    619     * @param angle2  the end angle (radians).
    620     * @param inc  the increment.
    621     * @param color  the color ({@code null} not permitted).
    622     *
    623     * @return  A pie segment object.
    624     */
    625    public static Object3D createPieSegment(double radius, double explodeRadius,
    626            double base, double height, double angle1, double angle2,
    627            double inc, Color color) {
    628        Args.nullNotPermitted(color, "color");
    629        Object3D segment = new Object3D(color, true);
    630        double angleCentre = (angle1 + angle2) / 2.0;
    631        Point3D centre = new Point3D(explodeRadius * Math.cos(angleCentre),
    632                base, explodeRadius * Math.sin(angleCentre));
    633        float cx = (float) centre.x;
    634        float cz = (float) centre.z;
    635        segment.addVertex(new Point3D(cx + 0.0, base, cz + 0.0));
    636        segment.addVertex(new Point3D(cx + 0.0, base + height, cz + 0.0));
    637        Point3D v0 = new Point3D(cx + radius * Math.cos(angle1), base,
    638                cz + radius * Math.sin(angle1));
    639        Point3D v1 = new Point3D(cx + radius * Math.cos(angle1), base + height,
    640                cz + radius * Math.sin(angle1));
    641        segment.addVertex(v0);
    642        segment.addVertex(v1);
    643        segment.addFace(new Face(segment, new int[] {1, 3, 2, 0}));
    644        int vc = 4; // vertex count
    645        double theta = angle1 + inc;
    646        while (theta < angle2) {
    647            Point3D v2 = new Point3D(cx + radius * Math.cos(theta), base,
    648                    cz + radius * Math.sin(theta));
    649            Point3D v3 = new Point3D(cx + radius * Math.cos(theta),
    650                    base + height, cz + radius * Math.sin(theta));
    651            segment.addVertex(v2);
    652            segment.addVertex(v3);
    653            vc = vc + 2;
    654
    655            // outside edge
    656            segment.addFace(new Face(segment,
    657                    new int[] {vc - 2, vc - 4, vc - 3, vc - 1}));
    658
    659            // top and bottom
    660            segment.addFace(new Face(segment,
    661                    new int[] {0,  vc - 4, vc - 2, 0}));
    662            segment.addFace(new Face(segment,
    663                    new int[] {1,  vc - 1, vc - 3, 1}));
    664            theta = theta + inc;
    665        }
    666        v0 = new Point3D(cx + radius * Math.cos(angle2), base,
    667                cz + radius * Math.sin(angle2));
    668        v1 = new Point3D(cx + radius * Math.cos(angle2), base + height,
    669                cz + radius * Math.sin(angle2));
    670        segment.addVertex(v0);
    671        segment.addVertex(v1);
    672        vc = vc + 2;
    673        segment.addFace(new Face(segment,
    674                new int[] {vc - 2, vc - 4, vc - 3, vc - 1}));
    675
    676        // top and bottom
    677        segment.addFace(new Face(segment, new int[] {0,  vc - 4, vc - 2, 0}));
    678        segment.addFace(new Face(segment, new int[] {1,  vc - 1, vc - 3, 1}));
    679
    680        // closing side
    681        segment.addFace(new Face(segment, new int[] {1, 0, vc-2, vc-1}));
    682        return segment;
    683    }
    684
    685    /**
    686     * Returns two 3D objects (sheets in the y-plane) that can be used as
    687     * alignment anchors for the labels of a pie segment.  One sheet is on the
    688     * front face of the segment, and the other is on the back face.  Depending
    689     * on the viewing point, only one of the sheets will be showing, and this
    690     * is the one that the pie segment label will be attached to.
    691     *
    692     * @param radius  the pie segment radius (in world units).
    693     * @param explodeRadius  the pie segment explode radius (in world units).
    694     * @param base  the base of the pie segment.
    695     * @param height  the height of the pie segment.
    696     * @param angle1  the start angle of the segment (in radians).
    697     * @param angle2  the end angle of the segment (in radians).
    698     *
    699     * @return A list containing the two 3D objects to be used as pie label
    700     *     markers.
    701     */
    702    public static List<Object3D> createPieLabelMarkers(double radius,
    703            double explodeRadius, double base, double height,
    704            double angle1, double angle2) {
    705        List<Object3D> result = new ArrayList<>();
    706        double angle = (angle1 + angle2) / 2.0;
    707        Point3D centre = new Point3D(explodeRadius * Math.cos(angle),
    708                base, explodeRadius * Math.sin(angle));
    709        float cx = (float) centre.x;
    710        float cz = (float) centre.z;
    711        double r = radius * 0.9;
    712        Point3D v0 = new Point3D(cx + r * Math.cos(angle), base,
    713                cz + r * Math.sin(angle));
    714        Point3D v1 = new Point3D(cx + r * Math.cos(angle), base + height,
    715                cz + r * Math.sin(angle));
    716        result.add(Object3D.createYSheet(2.0, v0.x, v0.y, v0.z, Color.RED,
    717                false));
    718        result.add(Object3D.createYSheet(2.0, v1.x, v1.y, v1.z, Color.BLUE,
    719                true));
    720        return result;
    721    }
    722
    723    /**
    724     * Creates a bar with the specified dimensions and color.
    725     *
    726     * @param xWidth  the x-width of the bar.
    727     * @param zWidth  the z-width (or depth) of the bar.
    728     * @param x  the x-coordinate for the center of the bar.
    729     * @param y  the y-coordinate for the top of the bar.
    730     * @param z  the z-coordinate for the center of the bar.
    731     * @param zero  the y-coordinate for the bottom of the bar.
    732     * @param barColor  the color for the bar ({@code null} not permitted).
    733     * @param baseColor  the color for the base of the bar (if {@code null},
    734     *     the {@code color} is used instead).
    735     * @param topColor  the color for the top of the bar (if
    736     *     {@code null}, the {@code color} is used instead).
    737     * @param inverted  a flag that determines whether the baseColor and
    738     *     topColor should be swapped in their usage.
    739     *
    740     * @return A 3D object that can represent a bar in a bar chart.
    741     */
    742    public static Object3D createBar(double xWidth, double zWidth, double x,
    743            double y, double z, double zero, Color barColor, Color baseColor,
    744            Color topColor, boolean inverted) {
    745        Args.nullNotPermitted(barColor, "barColor");
    746        Color c0 = baseColor;
    747        Color c1 = topColor;
    748        if (inverted) {
    749            Color cc = c1;
    750            c1 = c0;
    751            c0 = cc;
    752        }
    753        Object3D bar = new Object3D(barColor);
    754        if (c0 != null) {
    755            bar.setProperty(COLOR_PREFIX + "c0", c0);
    756        }
    757        if (c1 != null) {
    758            bar.setProperty(COLOR_PREFIX + "c1", c1);
    759        }
    760        double xdelta = xWidth / 2.0;
    761        double zdelta = zWidth / 2.0;
    762        bar.addVertex(new Point3D(x - xdelta, zero, z - zdelta));
    763        bar.addVertex(new Point3D(x + xdelta, zero, z - zdelta));
    764        bar.addVertex(new Point3D(x + xdelta, zero, z + zdelta));
    765        bar.addVertex(new Point3D(x - xdelta, zero, z + zdelta));
    766        bar.addVertex(new Point3D(x - xdelta, y, z - zdelta));
    767        bar.addVertex(new Point3D(x + xdelta, y, z - zdelta));
    768        bar.addVertex(new Point3D(x + xdelta, y, z + zdelta));
    769        bar.addVertex(new Point3D(x - xdelta, y, z + zdelta));
    770
    771        bar.addFace(new Face(bar, new int[] {0, 1, 5, 4}));
    772        bar.addFace(new Face(bar, new int[] {4, 5, 1, 0}));
    773        bar.addFace(new Face(bar, new int[] {1, 2, 6, 5}));
    774        bar.addFace(new Face(bar, new int[] {5, 6, 2, 1}));
    775        bar.addFace(new Face(bar, new int[] {2, 3, 7, 6}));
    776        bar.addFace(new Face(bar, new int[] {6, 7, 3, 2}));
    777        bar.addFace(new Face(bar, new int[] {0, 4, 7, 3}));
    778        bar.addFace(new Face(bar, new int[] {3, 7, 4, 0}));
    779        bar.addFace(new Face(bar, new int[] {4, 5, 6, 7}));
    780        bar.addFace(new Face(bar, new int[] {3, 2, 1, 0}));
    781        if (c1 != null) {
    782            bar.addFace(new TaggedFace(bar, new int[] {7, 6, 5, 4}, "c1"));
    783        } else {
    784            bar.addFace(new Face(bar, new int[] {7, 6, 5, 4}));
    785        }
    786        if (c0 != null) {
    787            bar.addFace(new TaggedFace(bar, new int[] {0, 1, 2, 3}, "c0"));
    788        } else {
    789            bar.addFace(new Face(bar, new int[] {0, 1, 2, 3}));
    790        }
    791
    792        return bar;
    793    }
    794
    795    /**
    796     * Creates a label object, which has a single transparent face in the
    797     * Z-plane plus associated label attributes.  These faces are used to
    798     * track the location and visibility of labels in a 3D scene.
    799     *
    800     * @param label  the label ({@code null} not permitted).
    801     * @param font  the font ({@code null} not permitted).
    802     * @param fgColor  the label foreground color ({@code null} not permitted).
    803     * @param bgColor  the label background color ({@code null} not permitted).
    804     * @param x  the x-coordinate in 3D space.
    805     * @param y  the y-coordinate in 3D space.
    806     * @param z  the z-coordinate in 3D space.
    807     * @param reversed  reverse the order of the vertices?
    808     * @param doubleSided  is the face double-sided (visible from either side)?
    809     *
    810     * @return A new label object (never {@code null}).
    811     *
    812     * @since 1.3
    813     */
    814    public static Object3D createLabelObject(String label, Font font,
    815            Color fgColor, Color bgColor, double x, double y, double z,
    816            boolean reversed, boolean doubleSided) {
    817        Object3D labelObj = new Object3D(bgColor);
    818        labelObj.setProperty(Object3D.CLASS_KEY, "ItemLabel");
    819        labelObj.addVertex(x - 0.1, y, z);
    820        labelObj.addVertex(x + 0.1, y, z);
    821        labelObj.addVertex(x + 0.1, y + 0.1, z);
    822        labelObj.addVertex(x - 0.1, y + 0.1, z);
    823
    824        if (!reversed || doubleSided) {
    825            labelObj.addFace(new LabelFace(labelObj, new int[] {0, 1, 2, 3},
    826                    label, font, fgColor, bgColor));
    827        }
    828        if (reversed || doubleSided) {
    829            labelObj.addFace(new LabelFace(labelObj, new int[] {3, 2, 1, 0},
    830                    label, font, fgColor, bgColor));
    831        }
    832        return labelObj;
    833    }
    834
    835}