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