/* =========================================================== * Orson Charts : a 3D chart library for the Java(tm) platform * =========================================================== * * (C)opyright 2013-2016, by Object Refinery Limited. All rights reserved. * * http://www.object-refinery.com/orsoncharts/index.html * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners.] * * If you do not wish to be bound by the terms of the GPL, an alternative * commercial license can be purchased. For details, please see visit the * Orson Charts home page: * * http://www.object-refinery.com/orsoncharts/index.html * */ package com.orsoncharts.graphics3d; import java.awt.Color; import java.awt.Font; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.orsoncharts.util.ArgChecks; /** * An object defined in 3D space by (a) a list of coordinates, and (b) a list * of faces. This class has methods to calculate projected points in 2D when * a {@link ViewPoint3D} is provided. * <br><br> * This class also contains a collection of static methods for constructing * common 3D objects. */ public class Object3D { /** * The key for storing the object class as an optional property for this * object. * * @since 1.4 */ public static final String CLASS_KEY = "class"; /** * The key for storing item keys as property values. * * @since 1.3 */ public static final String ITEM_KEY = "key"; /** * A prefix used for setting color properties for an object. * * @since 1.3 */ public static final String COLOR_PREFIX = "color/"; /** World coordinates. */ private List<Point3D> vertices; /** Faces for the object, specified by indices to the world coords. */ private List<Face> faces; /** The primary color for the object. */ private Color color; /** * A flag that indicates whether or not faces for this object have their * outlines drawn (that is, the shape is filled then drawn versus just * filled only). */ private boolean outline; /** * A map containing properties for the object. If there are no properties * defined, then we leave this as {@code null} as an empty map would * consume memory unnecessarily. */ private Map<String, Object> properties; /** * Creates a new object, initially with no vertices or faces. * * @param color the default face color ({@code null} not permitted). * * @since 1.3 */ public Object3D(Color color) { this(color, false); } /** * Creates a new object, initially with no vertices or faces. * * @param color the default face color ({@code null} not permitted). * @param outline the default flag that determines whether face outlines * are drawn. * * @since 1.3 */ public Object3D(Color color, boolean outline) { ArgChecks.nullNotPermitted(color, "color"); this.color = color; this.outline = outline; this.vertices = new java.util.ArrayList<Point3D>(); this.faces = new java.util.ArrayList<Face>(); } /** * Returns the default face color as specified in the constructor. * * @return The color (never {@code null}). * * @since 1.3 */ public Color getColor() { return this.color; } /** * Returns the outline flag. * * @return The outline flag. * * @since 1.3 */ public boolean getOutline() { return this.outline; } /** * Sets the outline flag. This determines the default setting for whether * or not the faces of this object have their outlines drawn when rendered. * * @param outline the new flag value. * * @since 1.3 */ public void setOutline(boolean outline) { this.outline = outline; } /** * Returns the value of the property with the specified key, or * {@code null} if there is no property defined for that key. * * @param key the property key ({@code null} not permitted). * * @return The value (possibly {@code null}). * * @since 1.3 */ public Object getProperty(String key) { ArgChecks.nullNotPermitted(key, "key"); if (this.properties == null) { return null; } else { return this.properties.get(key); } } /** * Sets the value of a property, overwriting any existing value. One * application for this is storing item key references to link a 3D object * back to the data item that it represents (the key for this is * {@link Object3D#ITEM_KEY}). * * @param key the key ({@code null} not permitted). * @param value the value ({@code null} permitted). * * @since 1.3 */ public void setProperty(String key, Object value) { ArgChecks.nullNotPermitted(key, "key"); if (this.properties == null) { this.properties = new HashMap<String, Object>(); } this.properties.put(key, value); } /** * Returns the color for a specific face. If the face has a tag, then * this method will look for a property with the key COLOR_PREFIX + tag * and return that color, otherwise it returns the default color for the * object. * * @param face the face ({@code null} not permitted). * * @return The color for the specified face (never {@code null}). * * @since 1.3 */ public Color getColor(Face face) { if (face.getTag() != null) { // see if there is a custom color defined for the tag Object obj = getProperty(COLOR_PREFIX + face.getTag()); if (obj != null) { return (Color) obj; } } return this.color; } /** * Returns {@code true} if an outline should be drawn for the * specified face, and {@code false} otherwise. * * @param face the face ({@code null} not permitted). * * @return A boolean. * * @since 1.3 */ public boolean getOutline(Face face) { return this.outline; } /** * Returns the number of vertices for this object. * * @return The number of vertices. */ public int getVertexCount() { return this.vertices.size(); } /** * Adds a new object vertex with the specified coordinates. * * @param x the x-coordinate. * @param y the y-coordinate. * @param z the z-coordinate. */ public void addVertex(double x, double y, double z) { addVertex(new Point3D(x, y, z)); } /** * Adds a new object vertex. * * @param vertex the vertex ({@code null} not permitted). */ public void addVertex(Point3D vertex) { ArgChecks.nullNotPermitted(vertex, "vertex"); this.vertices.add(vertex); } /** * Returns the number of faces. * * @return The number of faces. */ public int getFaceCount() { return this.faces.size(); } /** * Adds a face for the given vertices (specified by index value). * * @param vertices the vertices (all should lie in a plane). * * @since 1.3 */ public void addFace(int[] vertices) { // defer the arg checks... addFace(new Face(this, vertices)); } /** * Adds a tagged face for the given vertices (specified by index value). * * @param vertices the vertices (all should lie in a plane). * @param tag the tag ({@code null} not permitted). * * @since 1.3 */ public void addFace(int[] vertices, String tag) { addFace(new TaggedFace(this, vertices, tag)); } /** * Adds a double-sided face for the given vertices (specified by index * value) and color. * * @param vertices the vertices (all should lie in a plane). * * @since 1.3 */ public void addDoubleSidedFace(int[] vertices) { addFace(new DoubleSidedFace(this, vertices)); } /** * Adds a face for this object. * * @param face the face ({@code null} not permitted). */ public void addFace(Face face) { ArgChecks.nullNotPermitted(face, "face"); this.faces.add(face); } /** * Returns the faces for this object. Note that the list returned is a * direct reference to the internal storage for this {@code Object3D} * instance, so callers should take care not to modify this list * unintentionally. * * @return The faces. */ public List<Face> getFaces() { return this.faces; } /** * Calculates the projected points for the object's vertices, for the * given viewpoint. * * @param viewPoint the view point ({@code null} not permitted). * @param d the projection distance. * * @return The projected points. */ public Point2D[] calculateProjectedPoints(ViewPoint3D viewPoint, double d) { ArgChecks.nullNotPermitted(viewPoint, "viewPoint"); Point2D[] result = new Point2D[this.vertices.size()]; int vertexCount = this.vertices.size(); for (int i = 0; i < vertexCount; i++) { Point3D p = this.vertices.get(i); result[i] = viewPoint.worldToScreen(p, d); } return result; } /** * Returns the eye coordinates of the object's vertices. * * @param viewPoint the view point ({@code null} not permitted). * * @return The eye coordinates. */ public Point3D[] calculateEyeCoordinates(ViewPoint3D viewPoint) { ArgChecks.nullNotPermitted(viewPoint, "viewPoint"); Point3D[] result = new Point3D[this.vertices.size()]; int i = 0; for (Point3D vertex : this.vertices) { result[i] = viewPoint.worldToEye(vertex); i++; } return result; } /** * Creates a square flat surface in the x-z plane (constant y) with a * single face. * * @param size the sheet size. * @param x the x-coordinate for the center of the square. * @param y the y-coordinate. * @param z the z-coordinate for the center of the square. * @param color the color ({@code null} not permitted). * @param invert invert the order of the face * * @return The sheet. */ public static Object3D createYSheet(double size, double x, double y, double z, Color color, boolean invert) { ArgChecks.nullNotPermitted(color, "color"); Object3D sheet = new Object3D(color); double delta = size / 2.0; sheet.addVertex(new Point3D(x + delta, y, z - delta)); sheet.addVertex(new Point3D(x + delta, y, z + delta)); sheet.addVertex(new Point3D(x - delta, y, z + delta)); sheet.addVertex(new Point3D(x - delta, y, z - delta)); if (invert) { sheet.addFace(new Face(sheet, new int[] {3, 2, 1, 0})); } else { sheet.addFace(new Face(sheet, new int[] {0, 1, 2, 3})); } return sheet; } /** * Creates a square flat surface in the x-y plane (constant z). * * @param size the sheet size. * @param x the x-coordinate of a point on the surface. * @param y the y-coordinate of a point on the surface. * @param z the z-coordinate of a point on the surface. * @param color the color. * * @return The sheet. */ public static Object3D createZSheet(double size, double x, double y, double z, Color color) { Object3D sheet = new Object3D(color); double delta = size / 2.0; sheet.addVertex(new Point3D(x + delta, y - delta, z)); sheet.addVertex(new Point3D(x + delta, y + delta, z)); sheet.addVertex(new Point3D(x - delta, y + delta, z)); sheet.addVertex(new Point3D(x - delta, y - delta, z)); sheet.addFace(new Face(sheet, new int[] {0, 1, 2, 3})); return sheet; } /** * Creates a cube centered on {@code (x, y, z)} with the specified * {@code size}. * * @param size the size. * @param x the x-offset. * @param y the y-offset. * @param z the z-offset. * @param color the color ({@code null} not permitted). * * @return The cube (never {@code null}). */ public static Object3D createCube(double size, double x, double y, double z, Color color) { return createBox(x, size, y, size, z, size, color); } /** * Creates a box centered on {@code (x, y, z)} with the specified * dimensions. * * @param x the x-coordinate. * @param xdim the length of the box in the x-dimension. * @param y the y-coordinate. * @param ydim the length of the box in the y-dimension. * @param z the z-coordinate. * @param zdim the length of the box in the y-dimension. * @param color the color ({@code null} not permitted). * * @return The box (never {@code null}). * * @see #createCube(double, double, double, double, java.awt.Color) */ public static Object3D createBox(double x, double xdim, double y, double ydim, double z, double zdim, Color color) { ArgChecks.nullNotPermitted(color, "color"); Object3D box = new Object3D(color); double xdelta = xdim / 2.0; double ydelta = ydim / 2.0; double zdelta = zdim / 2.0; box.addVertex(new Point3D(x - xdelta, y - ydelta, z - zdelta)); box.addVertex(new Point3D(x + xdelta, y - ydelta, z - zdelta)); box.addVertex(new Point3D(x + xdelta, y - ydelta, z + zdelta)); box.addVertex(new Point3D(x - xdelta, y - ydelta, z + zdelta)); box.addVertex(new Point3D(x - xdelta, y + ydelta, z - zdelta)); box.addVertex(new Point3D(x + xdelta, y + ydelta, z - zdelta)); box.addVertex(new Point3D(x + xdelta, y + ydelta, z + zdelta)); box.addVertex(new Point3D(x - xdelta, y + ydelta, z + zdelta)); box.addFace(new Face(box, new int[] {4, 5, 1, 0})); box.addFace(new Face(box, new int[] {5, 6, 2, 1})); box.addFace(new Face(box, new int[] {6, 7, 3, 2})); box.addFace(new Face(box, new int[] {3, 7, 4, 0})); box.addFace(new Face(box, new int[] {7, 6, 5, 4})); box.addFace(new Face(box, new int[] {0, 1, 2, 3})); return box; } /** * Creates a tetrahedron. * * @param size the size. * @param xOffset the x-offset. * @param yOffset the y-offset. * @param zOffset the z-offset. * @param color the color ({@code null} not permitted). * * @return A tetrahedron. */ public static Object3D createTetrahedron(double size, double xOffset, double yOffset, double zOffset, Color color) { ArgChecks.nullNotPermitted(color, "color"); Object3D tetra = new Object3D(color); tetra.addVertex(new Point3D(size + xOffset, -size + yOffset, -size + zOffset)); tetra.addVertex(new Point3D(-size + xOffset, size + yOffset, -size + zOffset)); tetra.addVertex(new Point3D(size + xOffset, size + yOffset, size + zOffset)); tetra.addVertex(new Point3D(-size + xOffset, -size + yOffset, size + zOffset)); tetra.addFace(new Face(tetra, new int[] {0, 1, 2})); tetra.addFace(new Face(tetra, new int[] {1, 3, 2})); tetra.addFace(new Face(tetra, new int[] {0, 3, 1})); tetra.addFace(new Face(tetra, new int[] {0, 2, 3})); return tetra; } /** * Creates an octahedron. * * @param size the size. * @param xOffset the x-offset. * @param yOffset the y-offset. * @param zOffset the z-offset. * @param color the color ({@code null} not permitted). * * @return An octahedron. */ public static Object3D createOctahedron(double size, double xOffset, double yOffset, double zOffset, Color color) { ArgChecks.nullNotPermitted(color, "color"); Object3D octa = new Object3D(color); octa.addVertex(new Point3D(size + xOffset, 0 + yOffset, 0 + zOffset)); octa.addVertex(new Point3D(0 + xOffset, size + yOffset, 0 + zOffset)); octa.addVertex(new Point3D(-size + xOffset, 0 + yOffset, 0 + zOffset)); octa.addVertex(new Point3D(0 + xOffset, -size + yOffset, 0 + zOffset)); octa.addVertex(new Point3D(0 + xOffset, 0 + yOffset, -size + zOffset)); octa.addVertex(new Point3D(0 + xOffset, 0 + yOffset, size + zOffset)); octa.addFace(new Face(octa, new int[] {0, 1, 5})); octa.addFace(new Face(octa, new int[] {1, 2, 5})); octa.addFace(new Face(octa, new int[] {2, 3, 5})); octa.addFace(new Face(octa, new int[] {3, 0, 5})); octa.addFace(new Face(octa, new int[] {1, 0, 4})); octa.addFace(new Face(octa, new int[] {2, 1, 4})); octa.addFace(new Face(octa, new int[] {3, 2, 4})); octa.addFace(new Face(octa, new int[] {0, 3, 4})); return octa; } /** * Creates an approximation of a sphere. * * @param radius the radius of the sphere (in world units). * @param n the number of layers. * @param x the x-coordinate of the center of the sphere. * @param y the y-coordinate of the center of the sphere. * @param z the z-coordinate of the center of the sphere. * @param extColor the exterior color ({@code null} not permitted). * @param intColor the interior color ({@code null} not permitted). * * @return A sphere. */ public static Object3D createSphere(double radius, int n, double x, double y, double z, Color extColor, Color intColor) { Object3D sphere = new Object3D(extColor); sphere.setProperty(COLOR_PREFIX + "interior", intColor); double theta = Math.PI / n; Point3D[] prevLayer = new Point3D[n * 2 + 1]; for (int i = 0; i <= n * 2; i++) { prevLayer[i] = new Point3D(x, y + radius, z); if (i != n * 2) { sphere.addVertex(prevLayer[i]); } } for (int layer = 1; layer < n; layer++) { Point3D[] currLayer = new Point3D[n * 2 + 1]; for (int i = 0; i <= n * 2; i++) { double xx = radius * Math.cos(i * theta) * Math.sin(layer * theta); double yy = radius * Math.cos(layer * theta); double zz = radius * Math.sin(i * theta) * Math.sin(layer * theta); currLayer[i] = new Point3D(x + xx, y + yy, z + zz); if (i != n * 2) { sphere.addVertex(currLayer[i]); } if (i > 0 && layer > 1) { if (i != n * 2) { Face f = new Face(sphere, new int[] { (layer - 1) * n * 2 + i - 1, (layer - 1) * n * 2 + i, layer * n * 2 + i, layer * n * 2 + i - 1}); sphere.addFace(f); f = new TaggedFace(sphere, new int[] { layer * n * 2 + i - 1, layer * n * 2 + i, (layer - 1) * n * 2 + i, (layer - 1) * n * 2 + i - 1}, "interior"); sphere.addFace(f); } else { sphere.addFace(new Face(sphere, new int[] { (layer - 1) * n * 2 + i - 1, (layer - 1) * n * 2, layer * n * 2, layer * n * 2 + i - 1})); sphere.addFace(new TaggedFace(sphere, new int[] { layer * n * 2 + i - 1, layer * n * 2, (layer - 1) * n * 2, (layer - 1) * n * 2 + i - 1}, "interior")); } } } } return sphere; } /** * Creates a pie segment with the specified attributes. * * @param radius the radius. * @param explodeRadius the explode radius (0.0 if not exploded). * @param base the base. * @param height the height. * @param angle1 the start angle (radians). * @param angle2 the end angle (radians). * @param inc the increment. * @param color the color ({@code null} not permitted). * * @return A pie segment object. */ public static Object3D createPieSegment(double radius, double explodeRadius, double base, double height, double angle1, double angle2, double inc, Color color) { ArgChecks.nullNotPermitted(color, "color"); Object3D segment = new Object3D(color, true); double angleCentre = (angle1 + angle2) / 2.0; Point3D centre = new Point3D(explodeRadius * Math.cos(angleCentre), base, explodeRadius * Math.sin(angleCentre)); float cx = (float) centre.x; float cz = (float) centre.z; segment.addVertex(new Point3D(cx + 0.0, base, cz + 0.0)); segment.addVertex(new Point3D(cx + 0.0, base + height, cz + 0.0)); Point3D v0 = new Point3D(cx + radius * Math.cos(angle1), base, cz + radius * Math.sin(angle1)); Point3D v1 = new Point3D(cx + radius * Math.cos(angle1), base + height, cz + radius * Math.sin(angle1)); segment.addVertex(v0); segment.addVertex(v1); segment.addFace(new Face(segment, new int[] {1, 3, 2, 0})); int vc = 4; // vertex count double theta = angle1 + inc; while (theta < angle2) { Point3D v2 = new Point3D(cx + radius * Math.cos(theta), base, cz + radius * Math.sin(theta)); Point3D v3 = new Point3D(cx + radius * Math.cos(theta), base + height, cz + radius * Math.sin(theta)); segment.addVertex(v2); segment.addVertex(v3); vc = vc + 2; // outside edge segment.addFace(new Face(segment, new int[] {vc - 2, vc - 4, vc - 3, vc - 1})); // top and bottom segment.addFace(new Face(segment, new int[] {0, vc - 4, vc - 2, 0})); segment.addFace(new Face(segment, new int[] {1, vc - 1, vc - 3, 1})); theta = theta + inc; } v0 = new Point3D(cx + radius * Math.cos(angle2), base, cz + radius * Math.sin(angle2)); v1 = new Point3D(cx + radius * Math.cos(angle2), base + height, cz + radius * Math.sin(angle2)); segment.addVertex(v0); segment.addVertex(v1); vc = vc + 2; segment.addFace(new Face(segment, new int[] {vc - 2, vc - 4, vc - 3, vc - 1})); // top and bottom segment.addFace(new Face(segment, new int[] {0, vc - 4, vc - 2, 0})); segment.addFace(new Face(segment, new int[] {1, vc - 1, vc - 3, 1})); // closing side segment.addFace(new Face(segment, new int[] {1, 0, vc-2, vc-1})); return segment; } /** * Returns two 3D objects (sheets in the y-plane) that can be used as * alignment anchors for the labels of a pie segment. One sheet is on the * front face of the segment, and the other is on the back face. Depending * on the viewing point, only one of the sheets will be showing, and this * is the one that the pie segment label will be attached to. * * @param radius the pie segment radius (in world units). * @param explodeRadius the pie segment explode radius (in world units). * @param base the base of the pie segment. * @param height the height of the pie segment. * @param angle1 the start angle of the segment (in radians). * @param angle2 the end angle of the segment (in radians). * * @return A list containing the two 3D objects to be used as pie label * markers. */ public static List<Object3D> createPieLabelMarkers(double radius, double explodeRadius, double base, double height, double angle1, double angle2) { List<Object3D> result = new ArrayList<Object3D>(); double angle = (angle1 + angle2) / 2.0; Point3D centre = new Point3D(explodeRadius * Math.cos(angle), base, explodeRadius * Math.sin(angle)); float cx = (float) centre.x; float cz = (float) centre.z; double r = radius * 0.9; Point3D v0 = new Point3D(cx + r * Math.cos(angle), base, cz + r * Math.sin(angle)); Point3D v1 = new Point3D(cx + r * Math.cos(angle), base + height, cz + r * Math.sin(angle)); result.add(Object3D.createYSheet(2.0, v0.x, v0.y, v0.z, Color.RED, false)); result.add(Object3D.createYSheet(2.0, v1.x, v1.y, v1.z, Color.BLUE, true)); return result; } /** * Creates a bar with the specified dimensions and color. * * @param xWidth the x-width of the bar. * @param zWidth the z-width (or depth) of the bar. * @param x the x-coordinate for the center of the bar. * @param y the y-coordinate for the top of the bar. * @param z the z-coordinate for the center of the bar. * @param zero the y-coordinate for the bottom of the bar. * @param barColor the color for the bar ({@code null} not permitted). * @param baseColor the color for the base of the bar (if {@code null}, * the {@code color} is used instead). * @param topColor the color for the top of the bar (if * {@code null}, the {@code color} is used instead). * @param inverted a flag that determines whether the baseColor and * topColor should be swapped in their usage. * * @return A 3D object that can represent a bar in a bar chart. */ public static Object3D createBar(double xWidth, double zWidth, double x, double y, double z, double zero, Color barColor, Color baseColor, Color topColor, boolean inverted) { ArgChecks.nullNotPermitted(barColor, "barColor"); Color c0 = baseColor; Color c1 = topColor; if (inverted) { Color cc = c1; c1 = c0; c0 = cc; } Object3D bar = new Object3D(barColor); if (c0 != null) { bar.setProperty(COLOR_PREFIX + "c0", c0); } if (c1 != null) { bar.setProperty(COLOR_PREFIX + "c1", c1); } double xdelta = xWidth / 2.0; double zdelta = zWidth / 2.0; bar.addVertex(new Point3D(x - xdelta, zero, z - zdelta)); bar.addVertex(new Point3D(x + xdelta, zero, z - zdelta)); bar.addVertex(new Point3D(x + xdelta, zero, z + zdelta)); bar.addVertex(new Point3D(x - xdelta, zero, z + zdelta)); bar.addVertex(new Point3D(x - xdelta, y, z - zdelta)); bar.addVertex(new Point3D(x + xdelta, y, z - zdelta)); bar.addVertex(new Point3D(x + xdelta, y, z + zdelta)); bar.addVertex(new Point3D(x - xdelta, y, z + zdelta)); bar.addFace(new Face(bar, new int[] {0, 1, 5, 4})); bar.addFace(new Face(bar, new int[] {4, 5, 1, 0})); bar.addFace(new Face(bar, new int[] {1, 2, 6, 5})); bar.addFace(new Face(bar, new int[] {5, 6, 2, 1})); bar.addFace(new Face(bar, new int[] {2, 3, 7, 6})); bar.addFace(new Face(bar, new int[] {6, 7, 3, 2})); bar.addFace(new Face(bar, new int[] {0, 4, 7, 3})); bar.addFace(new Face(bar, new int[] {3, 7, 4, 0})); bar.addFace(new Face(bar, new int[] {4, 5, 6, 7})); bar.addFace(new Face(bar, new int[] {3, 2, 1, 0})); if (c1 != null) { bar.addFace(new TaggedFace(bar, new int[] {7, 6, 5, 4}, "c1")); } else { bar.addFace(new Face(bar, new int[] {7, 6, 5, 4})); } if (c0 != null) { bar.addFace(new TaggedFace(bar, new int[] {0, 1, 2, 3}, "c0")); } else { bar.addFace(new Face(bar, new int[] {0, 1, 2, 3})); } return bar; } /** * Creates a label object, which has a single transparent face in the * Z-plane plus associated label attributes. These faces are used to * track the location and visibility of labels in a 3D scene. * * @param label the label ({@code null} not permitted). * @param font the font ({@code null} not permitted). * @param fgColor the label foreground color ({@code null} not permitted). * @param bgColor the label background color ({@code null} not permitted). * @param x the x-coordinate in 3D space. * @param y the y-coordinate in 3D space. * @param z the z-coordinate in 3D space. * @param reversed reverse the order of the vertices? * @param doubleSided is the face double-sided (visible from either side)? * * @return A new label object (never {@code null}). * * @since 1.3 */ public static Object3D createLabelObject(String label, Font font, Color fgColor, Color bgColor, double x, double y, double z, boolean reversed, boolean doubleSided) { Object3D labelObj = new Object3D(bgColor); labelObj.setProperty(Object3D.CLASS_KEY, "ItemLabel"); labelObj.addVertex(x - 0.1, y, z); labelObj.addVertex(x + 0.1, y, z); labelObj.addVertex(x + 0.1, y + 0.1, z); labelObj.addVertex(x - 0.1, y + 0.1, z); if (!reversed || doubleSided) { labelObj.addFace(new LabelFace(labelObj, new int[] {0, 1, 2, 3}, label, font, fgColor, bgColor)); } if (reversed || doubleSided) { labelObj.addFace(new LabelFace(labelObj, new int[] {3, 2, 1, 0}, label, font, fgColor, bgColor)); } return labelObj; } }