/* =========================================================== * 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.renderer.category; import java.awt.Color; import java.io.Serializable; import com.orsoncharts.Chart3D; import com.orsoncharts.Chart3DFactory; import com.orsoncharts.Range; import com.orsoncharts.axis.CategoryAxis3D; import com.orsoncharts.axis.ValueAxis3D; import com.orsoncharts.data.KeyedValues3DItemKey; import com.orsoncharts.data.category.CategoryDataset3D; import com.orsoncharts.graphics3d.Dimension3D; import com.orsoncharts.graphics3d.Object3D; import com.orsoncharts.graphics3d.Offset3D; import com.orsoncharts.graphics3d.World; import com.orsoncharts.label.ItemLabelPositioning; import com.orsoncharts.plot.CategoryPlot3D; import com.orsoncharts.renderer.Renderer3DChangeEvent; import com.orsoncharts.util.ObjectUtils; /** * A renderer that can be used with the {@link CategoryPlot3D} class to create * 3D lines charts from data in a {@link CategoryDataset3D}. The * {@code createLineChart()} method in the {@link Chart3DFactory} class * will construct a chart that uses this renderer. Here is a sample: * <div> * <object id="ABC" data="../../../../doc-files/LineChart3DDemo1.svg" * type="image/svg+xml" width="500" height="359"></object> * </div> * (refer to {@code LineChart3DDemo1.java} for the code to generate the * above chart). * <br><br> * Some attributes in the renderer are specified in "world units" - see the * {@link Chart3D} class description for more information about world units. * <br><br> * There is a factory method to create a chart using this renderer - see * {@link Chart3DFactory#createLineChart(String, String, CategoryDataset3D, * String, String, String)}. * <br><br> * NOTE: This class is serializable, but the serialization format is subject * to change in future releases and should not be relied upon for persisting * instances of this class. */ @SuppressWarnings("serial") public class LineRenderer3D extends AbstractCategoryRenderer3D implements Serializable { /** The line width (in world units). */ private double lineWidth; /** The line height (in world units). */ private double lineHeight; /** * For isolated data values this attribute controls the width (x-axis) of * the box representing the data item, it is expressed as a percentage of * the category width. */ private double isolatedItemWidthPercent; /** * The color source that determines the color used to highlight clipped * items in the chart. */ private CategoryColorSource clipColorSource; /** * Creates a new instance with default attribute values. */ public LineRenderer3D() { this.lineWidth = 0.4; this.lineHeight = 0.2; this.isolatedItemWidthPercent = 0.25; this.clipColorSource = new StandardCategoryColorSource(Color.RED); } /** * Returns the line width in world units. The default value is * {@code 0.4}. * * @return The line width in world units. */ public double getLineWidth() { return this.lineWidth; } /** * Sets the line width (in world units) and sends a * {@link Renderer3DChangeEvent} to all registered listeners. * * @param width the width (in world units). */ public void setLineWidth(double width) { this.lineWidth = width; fireChangeEvent(true); } /** * Returns the line height in world units. The default value is * {@code 0.2}. * * @return The line height in world units. */ public double getLineHeight() { return this.lineHeight; } /** * Sets the line height (in world units) and sends a * {@link Renderer3DChangeEvent} to all registered listeners. * * @param height the height (in world units). */ public void setLineHeight(double height) { this.lineHeight = height; fireChangeEvent(true); } /** * Returns the width for isolated data items as a percentage of the * category width. The default value is 0.25 (twenty five percent). * * @return The width percentage. * * @since 1.3 */ public double getIsolatedItemWidthPercent() { return this.isolatedItemWidthPercent; } /** * Sets the width for isolated data items as a percentage of the category * width and sends a change event to all registered listeners. * * @param percent the new percentage. * * @since 1.3 */ public void setIsolatedItemWidthPercent(double percent) { this.isolatedItemWidthPercent = percent; fireChangeEvent(true); } /** * Returns the color source used to determine the color used to highlight * clipping in the chart elements. If the source is {@code null}, * then the regular series color is used instead. * * @return The color source (possibly {@code null}). */ public CategoryColorSource getClipColorSource() { return this.clipColorSource; } /** * Sets the color source that determines the color used to highlight * clipping in the chart elements, and sends a {@link Renderer3DChangeEvent} * to all registered listeners. * * @param source the source ({@code null} permitted). */ public void setClipColorSource(CategoryColorSource source) { this.clipColorSource = source; fireChangeEvent(true); } /** * Constructs and places one item from the specified dataset into the given * world. This method will be called by the {@link CategoryPlot3D} class * while iterating over the items in the dataset. * * @param dataset the dataset ({@code null} not permitted). * @param series the series index. * @param row the row index. * @param column the column index. * @param world the world ({@code null} not permitted). * @param dimensions the plot dimensions ({@code null} not permitted). * @param xOffset the x-offset. * @param yOffset the y-offset. * @param zOffset the z-offset. */ @Override @SuppressWarnings("unchecked") public void composeItem(CategoryDataset3D dataset, int series, int row, int column, World world, Dimension3D dimensions, double xOffset, double yOffset, double zOffset) { // there is a lot of brute force code underneath this compose method // because I haven't seen the pattern yet that will let me reduce it // to something more elegant...probably I'm not smart enough. Number y = (Number) dataset.getValue(series, row, column); Number yprev = null; if (column > 0) { yprev = (Number) dataset.getValue(series, row, column - 1); } Number ynext = null; if (column < dataset.getColumnCount() - 1) { ynext = (Number) dataset.getValue(series, row, column + 1); } CategoryPlot3D plot = getPlot(); CategoryAxis3D rowAxis = plot.getRowAxis(); CategoryAxis3D columnAxis = plot.getColumnAxis(); ValueAxis3D valueAxis = plot.getValueAxis(); Range r = valueAxis.getRange(); Comparable<?> seriesKey = dataset.getSeriesKey(series); Comparable<?> rowKey = dataset.getRowKey(row); Comparable<?> columnKey = dataset.getColumnKey(column); double rowValue = rowAxis.getCategoryValue(rowKey); double columnValue = columnAxis.getCategoryValue(columnKey); double ww = dimensions.getWidth(); double hh = dimensions.getHeight(); double dd = dimensions.getDepth(); // for any data value, we'll try to create two line segments, one to // the left of the value and one to the right of the value (each // halfway to the adjacent data value). If the adjacent data values // are null (or don't exist, as in the case of the first and last data // items, then we can create an isolated segment to represent the data // item. The final consideration is whether the opening and closing // faces of each segment are filled or not (if the segment connects to // another segment, there is no need to fill the end face) boolean createLeftSegment, createRightSegment, createIsolatedSegment; boolean leftOpen = false; boolean leftClose = false; boolean rightOpen = false; boolean rightClose = false; if (column == 0) { // first column is a special case createLeftSegment = false; // never for first item if (dataset.getColumnCount() == 1) { createRightSegment = false; createIsolatedSegment = (y != null); } else { createRightSegment = (y != null && ynext != null); rightOpen = true; rightClose = false; createIsolatedSegment = (y != null && ynext == null); } } else if (column == dataset.getColumnCount() - 1) { // last column createRightSegment = false; // never for the last item createLeftSegment = (y != null && yprev != null); leftOpen = false; leftClose = true; createIsolatedSegment = (y != null && yprev == null); } else { // this is the general case createLeftSegment = (y != null && yprev != null); leftOpen = false; leftClose = (createLeftSegment && ynext == null); createRightSegment = (y != null && ynext != null); rightOpen = (createRightSegment && yprev == null); rightClose = false; createIsolatedSegment = (y != null && yprev == null && ynext == null); } // now that we know what we have to create, we'll need some info // for the construction double xw = columnAxis.translateToWorld(columnValue, ww) + xOffset; double yw = Double.NaN; if (y != null) { yw = valueAxis.translateToWorld(y.doubleValue(), hh) + yOffset; } double zw = rowAxis.translateToWorld(rowValue, dd) + zOffset; double ywmin = valueAxis.translateToWorld(r.getMin(), hh) + yOffset; double ywmax = valueAxis.translateToWorld(r.getMax(), hh) + yOffset; Color color = getColorSource().getColor(series, row, column); Color clipColor = color; if (getClipColorSource() != null) { Color c = getClipColorSource().getColor(series, row, column); if (c != null) { clipColor = c; } } KeyedValues3DItemKey itemKey = new KeyedValues3DItemKey(seriesKey, rowKey, columnKey); if (createLeftSegment) { Comparable<?> prevColumnKey = dataset.getColumnKey(column - 1); double prevColumnValue = columnAxis.getCategoryValue(prevColumnKey); double prevColumnX = columnAxis.translateToWorld(prevColumnValue, ww) + xOffset; double xl = (prevColumnX + xw) / 2.0; double yprevw = valueAxis.translateToWorld(yprev.doubleValue(), hh) + yOffset; double yl = (yprevw + yw) / 2.0; Object3D left = createSegment(xl, yl, xw, yw, zw, this.lineWidth, this.lineHeight, ywmin, ywmax, color, clipColor, leftOpen, leftClose); if (left != null) { left.setProperty(Object3D.ITEM_KEY, itemKey); world.add(left); } } if (createRightSegment) { Comparable<?> nextColumnKey = dataset.getColumnKey(column + 1); double nextColumnValue = columnAxis.getCategoryValue(nextColumnKey); double nextColumnX = columnAxis.translateToWorld(nextColumnValue, ww) + xOffset; double xr = (nextColumnX + xw) / 2.0; double ynextw = valueAxis.translateToWorld(ynext.doubleValue(), hh) + yOffset; double yr = (ynextw + yw) / 2.0; Object3D right = createSegment(xw, yw, xr, yr, zw, this.lineWidth, this.lineHeight, ywmin, ywmax, color, clipColor, rightOpen, rightClose); if (right != null) { right.setProperty(Object3D.ITEM_KEY, itemKey); world.add(right); } } if (createIsolatedSegment) { double cw = columnAxis.getCategoryWidth() * this.isolatedItemWidthPercent; double cww = columnAxis.translateToWorld(cw, ww); Object3D isolated = Object3D.createBox(xw, cww, yw, this.lineHeight, zw, this.lineWidth, color); if (isolated != null) { isolated.setProperty(Object3D.ITEM_KEY, itemKey); world.add(isolated); } } if (getItemLabelGenerator() != null && !Double.isNaN(yw) && yw >= ywmin && yw <= ywmax) { String label = getItemLabelGenerator().generateItemLabel(dataset, seriesKey, rowKey, columnKey); if (label != null) { ItemLabelPositioning positioning = getItemLabelPositioning(); Offset3D offsets = getItemLabelOffsets(); double dy = offsets.getDY() * dimensions.getHeight(); if (positioning.equals(ItemLabelPositioning.CENTRAL)) { Object3D labelObj = Object3D.createLabelObject(label, getItemLabelFont(), getItemLabelColor(), getItemLabelBackgroundColor(), xw, yw + dy, zw, false, true); labelObj.setProperty(Object3D.ITEM_KEY, itemKey); world.add(labelObj); } else if (positioning.equals( ItemLabelPositioning.FRONT_AND_BACK)) { double dz = this.lineWidth ; Object3D labelObj1 = Object3D.createLabelObject(label, getItemLabelFont(), getItemLabelColor(), getItemLabelBackgroundColor(), xw, yw, zw - dz, false, false); labelObj1.setProperty(Object3D.ITEM_KEY, itemKey); world.add(labelObj1); Object3D labelObj2 = Object3D.createLabelObject(label, getItemLabelFont(), getItemLabelColor(), getItemLabelBackgroundColor(), xw, yw, zw + dz, true, false); labelObj2.setProperty(Object3D.ITEM_KEY, itemKey); world.add(labelObj2); } } } } /** * Creates a segment of a line between (x0, y0, z) and (x1, y1, z), with * the specified line width and height, taking into account the minimum * and maximum world coordinates (in the y-direction, because it is assumed * that we have the full x and z-range required). * * @param x0 the starting x-coordinate. * @param y0 the starting x-coordinate. * @param x1 the ending x-coordinate. * @param y1 the ending y-coordinate. * @param z the z-coordinate. * @param lineWidth the line width (z-axis). * @param lineHeight the line height (y-axis). * @param ymin the lower bound for y-values. * @param ymax the upper bound for y-values. * @param color the segment color. * @param clipColor the clip color (for the faces in the segment that are * clipped against the edge of the world). * @param openingFace is an opening face required? * @param closingFace is a closing face required? * * @return A 3D object that is a segment in a line. */ private Object3D createSegment(double x0, double y0, double x1, double y1, double z, double lineWidth, double lineHeight, double ymin, double ymax, Color color, Color clipColor, boolean openingFace, boolean closingFace) { double wdelta = lineWidth / 2.0; double hdelta = lineHeight / 2.0; double y0b = y0 - hdelta; double y0t = y0 + hdelta; double y1b = y1 - hdelta; double y1t = y1 + hdelta; double zf = z - wdelta; double zb = z + wdelta; double[] xpts = calcCrossPoints(x0, x1, y0b, y0t, y1b, y1t, ymin, ymax); Object3D seg = null; if (y0b >= ymax) { // CASE A seg = createSegmentA(x0, x1, xpts, y0b, y0t, y1b, y1t, ymin, ymax, zf, zb, color, clipColor, false, closingFace); } else if (y0t > ymax && y0b > ymin) { // CASE B seg = createSegmentB(x0, x1, xpts, y0b, y0t, y1b, y1t, ymin, ymax, zf, zb, color, clipColor, openingFace, closingFace); } else if (y0t > ymax && y0b <= ymin) { // CASE C seg = createSegmentC(x0, x1, xpts, y0b, y0t, y1b, y1t, ymin, ymax, zf, zb, color, clipColor, openingFace, closingFace); } else if (y0t > ymin && y0b >= ymin) { // CASE D seg = createSegmentD(x0, x1, xpts, y0b, y0t, y1b, y1t, ymin, ymax, zf, zb, color, clipColor, openingFace, closingFace); } else if (y0t > ymin && y0b < ymin) { // CASE E seg = createSegmentE(x0, x1, xpts, y0b, y0t, y1b, y1t, ymin, ymax, zf, zb, color, clipColor, openingFace, closingFace); } else if (y0t <= ymin) { // CASE F seg = createSegmentF(x0, x1, xpts, y0b, y0t, y1b, y1t, ymin, ymax, zf, zb, color, clipColor, false, closingFace); } return seg; } /** * Calculates the four intersection points between two horizontal lines * (ymin and ymax) and the lines (x0, y0b, x1, y1b) and (x0, y1t, x1, y1t) * and returns the x-coordinates in an array. * * @param x0 * @param x1 * @param y0b * @param y0t * @param y1b * @param y1t * @param ymin * @param ymax * * @return An array of 4 x-coordinates. */ private double[] calcCrossPoints(double x0, double x1, double y0b, double y0t, double y1b, double y1t, double ymin, double ymax) { double[] xpts = new double[] { Double.NaN, Double.NaN, Double.NaN, Double.NaN }; double factor = (y0b - ymin) / (y0b - y1b); xpts[0] = x0 + factor * (x1 - x0); factor = (y0t - ymin) / (y0t - y1t); xpts[1] = x0 + factor * (x1 - x0); factor = (y0b - ymax) / (y0b - y1b); xpts[2] = x0 + factor * (x1 - x0); factor = (y0t - ymax) / (y0t - y1t); xpts[3] = x0 + factor * (x1 - x0); return xpts; } /** * Creates a segment for the case where the start of the segment is * completely above the upper bound of the axis at the left side of the * chart. * * @param x0 * @param x1 * @param xpts * @param y0b * @param y0t * @param y1b * @param y1t * @param wmin * @param wmax * @param zf * @param zb * @param color * @param clipColor * @param openingFace ignored because there is no opening face for this * case. * @param closingFace * * @return A segment ({@code null} if the segment is entirely clipped). */ private Object3D createSegmentA(double x0, double x1, double[] xpts, double y0b, double y0t, double y1b, double y1t, double wmin, double wmax, double zf, double zb, Color color, Color clipColor, boolean openingFace, boolean closingFace) { if (y1b > wmax) { return null; // nothing is visible } if (y1t > wmax) { if (y1b >= wmin) { // create a triangle with the top and right Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(xpts[2], wmax, zf); seg.addVertex(xpts[2], wmax, zb); seg.addVertex(x1, wmax, zf); seg.addVertex(x1, wmax, zb); seg.addVertex(x1, y1b, zf); seg.addVertex(x1, y1b, zb); seg.addFace(new int[] {0, 2, 4}); // front seg.addFace(new int[] {1, 5, 3}); // rear seg.addFace(new int[] {0, 1, 3, 2}, "clip"); // clip top seg.addFace(new int[] {4, 5, 1, 0}); // bottom if (closingFace) { seg.addFace(new int[] {2, 3, 5, 4}); } return seg; } else { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(xpts[2], wmax, zf); seg.addVertex(xpts[2], wmax, zb); seg.addVertex(x1, wmax, zf); seg.addVertex(x1, wmax, zb); seg.addVertex(x1, wmin, zf); seg.addVertex(x1, wmin, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {0, 1, 3, 2}); // clip top seg.addFace(new int[] {4, 5, 7, 6}, "clip"); // clip bottom seg.addFace(new int[] {6, 7, 1, 0}); // bottom if (closingFace) { seg.addFace(new int[] {2, 3, 5, 4}); } return seg; } } else if (y1t >= wmin) { if (y1b >= wmin) { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(xpts[2], wmax, zf); seg.addVertex(xpts[2], wmax, zb); seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(x1, y1t, zf); seg.addVertex(x1, y1t, zb); seg.addVertex(x1, y1b, zf); seg.addVertex(x1, y1b, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {0, 1, 3, 2}, "clip"); // clip top seg.addFace(new int[] {2, 3, 5, 4}); // top seg.addFace(new int[] {6, 7, 1, 0}); // bottom if (closingFace) { seg.addFace(new int[] {4, 5, 7, 6}); } return seg; } else { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(xpts[2], wmax, zf); seg.addVertex(xpts[2], wmax, zb); seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(x1, y1t, zf); seg.addVertex(x1, y1t, zb); seg.addVertex(x1, wmin, zf); seg.addVertex(x1, wmin, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6, 8}); // front seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); seg.addFace(new int[] {0, 1, 3, 2}, "clip"); // clip top seg.addFace(new int[] {6, 7, 9, 8}, "clip"); // clip bottom seg.addFace(new int[] {8, 9, 1, 0}); // there is no opening face in this case if (closingFace) { seg.addFace(new int[] {4, 5, 7, 6}); } return seg; } } else { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(xpts[2], wmax, zf); seg.addVertex(xpts[2], wmax, zb); seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(xpts[1], wmin, zf); seg.addVertex(xpts[1], wmin, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {4, 2, 3, 5}); // top seg.addFace(new int[] {0, 6, 7, 1}); // bottom seg.addFace(new int[] {0, 1, 3, 2}, "clip"); // clip top seg.addFace(new int[] {4, 5, 7, 6}, "clip"); // clip bottom // there are no opening or closing faces in this case return seg; } } /** * Creates a segment for the case where the left end of the line spans * the axis maximum on the left side of the chart. * * @param x0 * @param x1 * @param xpts * @param y0b * @param y0t * @param y1b * @param y1t * @param wmin * @param wmax * @param zf * @param zb * @param color * @param clipColor * @param openingFace * @param closingFace * * @return A segment. */ private Object3D createSegmentB(double x0, double x1, double[] xpts, double y0b, double y0t, double y1b, double y1t, double wmin, double wmax, double zf, double zb, Color color, Color clipColor, boolean openingFace, boolean closingFace) { if (y1b >= wmax) { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, y0b, zf); seg.addVertex(x0, y0b, zb); seg.addVertex(x0, wmax, zf); seg.addVertex(x0, wmax, zb); seg.addVertex(xpts[2], wmax, zf); seg.addVertex(xpts[2], wmax, zb); seg.addFace(new int[] {0, 2, 4}); // front seg.addFace(new int[] {1, 5, 3}); // rear seg.addFace(new int[] {0, 4, 5, 1}); // bottom seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } // there is no closing face in this case return seg; } if (y1t > wmax) { if (y1b >= wmin) { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, y0b, zf); seg.addVertex(x0, y0b, zb); seg.addVertex(x0, wmax, zf); seg.addVertex(x0, wmax, zb); seg.addVertex(x1, wmax, zf); seg.addVertex(x1, wmax, zb); seg.addVertex(x1, y1b, zf); seg.addVertex(x1, y1b, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top seg.addFace(new int[] {0, 6, 7, 1}); // bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } if (closingFace) { seg.addFace(new int[] {4, 5, 7, 6}); } return seg; } else { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, y0b, zf); seg.addVertex(x0, y0b, zb); seg.addVertex(x0, wmax, zf); seg.addVertex(x0, wmax, zb); seg.addVertex(x1, wmax, zf); seg.addVertex(x1, wmax, zb); seg.addVertex(x1, wmin, zf); seg.addVertex(x1, wmin, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6, 8}); // front seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top seg.addFace(new int[] {8, 6, 7, 9}, "clip"); // clip bottom seg.addFace(new int[] {0, 8, 9, 1}); if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } if (closingFace) { seg.addFace(new int[] {4, 5, 7, 6}); } return seg; } } if (y1t > wmin) { if (y1b >= wmin) { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, y0b, zf); seg.addVertex(x0, y0b, zb); seg.addVertex(x0, wmax, zf); seg.addVertex(x0, wmax, zb); seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(x1, y1t, zf); seg.addVertex(x1, y1t, zb); seg.addVertex(x1, y1b, zf); seg.addVertex(x1, y1b, zb); seg.addFace(new int[] {0, 2, 4, 6, 8}); // front seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top seg.addFace(new int[] {4, 5, 7, 6}); // top seg.addFace(new int[] {0, 8, 9, 1}); // bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } if (closingFace) { seg.addFace(new int[] {6, 7, 9, 8}); } return seg; } else { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, y0b, zf); seg.addVertex(x0, y0b, zb); seg.addVertex(x0, wmax, zf); seg.addVertex(x0, wmax, zb); seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(x1, y1t, zf); seg.addVertex(x1, y1t, zb); seg.addVertex(x1, wmin, zf); seg.addVertex(x1, wmin, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6, 8, 10}); // front seg.addFace(new int[] {1, 11, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top seg.addFace(new int[] {4, 5, 7, 6}); // top seg.addFace(new int[] {8, 9, 11, 10}, "clip"); // clip bottom seg.addFace(new int[] {10, 11, 1, 0}); // bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } if (closingFace) { seg.addFace(new int[] {6, 7, 9, 8}); } return seg; } } Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, y0b, zf); seg.addVertex(x0, y0b, zb); seg.addVertex(x0, wmax, zf); seg.addVertex(x0, wmax, zb); seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(xpts[1], wmin, zf); seg.addVertex(xpts[1], wmin, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6, 8}); // front seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top seg.addFace(new int[] {4, 5, 7, 6}); // top seg.addFace(new int[] {6, 7, 9, 8}, "clip"); // clip bottom seg.addFace(new int[] {8, 9, 1, 0}); // bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } // there is no closing face in this case return seg; } /** * Creates a segment for the case where the line end spans the entire axis * range at the left side of the chart. * * @param x0 * @param x1 * @param xpts * @param y0b * @param y0t * @param y1b * @param y1t * @param wmin * @param wmax * @param zf * @param zb * @param color * @param clipColor * @param openingFace * @param closingFace * * @return A segment. */ private Object3D createSegmentC(double x0, double x1, double[] xpts, double y0b, double y0t, double y1b, double y1t, double wmin, double wmax, double zf, double zb, Color color, Color clipColor, boolean openingFace, boolean closingFace) { // the first 4 vertices and the opening face are common to all // segments in this case Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, wmin, zf); seg.addVertex(x0, wmin, zb); seg.addVertex(x0, wmax, zf); seg.addVertex(x0, wmax, zb); if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } if (y1b >= wmax) { seg.addVertex(xpts[2], wmax, zf); seg.addVertex(xpts[2], wmax, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top seg.addFace(new int[] {4, 5, 7, 6}); // bottom seg.addFace(new int[] {7, 1, 0, 6}, "clip"); // bottom clip return seg; } if (y1t > wmax) { if (y1b >= wmin) { seg.addVertex(x1, wmax, zf); seg.addVertex(x1, wmax, zb); seg.addVertex(x1, y1b, zf); seg.addVertex(x1, y1b, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6, 8}); // front seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // top clip seg.addFace(new int[] {6, 7, 9, 8}); // bottom seg.addFace(new int[] {8, 9, 1, 0}, "clip"); // clip bottom if (closingFace) { seg.addFace(new int[] {4, 5, 7, 6}); } return seg; } else { seg.addVertex(x1, wmax, zf); seg.addVertex(x1, wmax, zb); seg.addVertex(x1, wmin, zf); seg.addVertex(x1, wmin, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top seg.addFace(new int[] {4, 5, 7, 6}); // bottom seg.addFace(new int[] {7, 1, 0, 6}, "clip"); // bottom clip return seg; } } if (y1t > wmin) { if (y1b >= wmin) { return null; // in practice I don't think this case // can occur } else { seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(x1, y1t, zf); seg.addVertex(x1, y1t, zb); seg.addVertex(x1, wmin, zf); seg.addVertex(x1, wmin, zb); seg.addFace(new int[] {0, 2, 4, 6, 8}); // front seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top seg.addFace(new int[] {4, 5, 7, 6}); // top seg.addFace(new int[] {9, 1, 0, 8}, "clip"); // clip bottom if (closingFace) { seg.addFace(new int[] {6, 7, 9, 8}); } return seg; } } seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(xpts[1], wmin, zf); seg.addVertex(xpts[1], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top seg.addFace(new int[] {4, 5, 7, 6}); // top seg.addFace(new int[] {6, 7, 1, 0}, "clip"); // clip bottom return seg; } /** * Creates a segment for the case where the segment is contained within * the axis range at the left side. * * @param x0 * @param x1 * @param xpts * @param y0b * @param y0t * @param y1b * @param y1t * @param wmin * @param wmax * @param zf * @param zb * @param color * @param clipColor * @param openingFace * @param closingFace * * @return A segment. */ private Object3D createSegmentD(double x0, double x1, double[] xpts, double y0b, double y0t, double y1b, double y1t, double wmin, double wmax, double zf, double zb, Color color, Color clipColor, boolean openingFace, boolean closingFace) { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, y0b, zf); seg.addVertex(x0, y0b, zb); seg.addVertex(x0, y0t, zf); seg.addVertex(x0, y0t, zb); if (y1b >= wmax) { seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(xpts[2], wmax, zf); seg.addVertex(xpts[2], wmax, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); // top seg.addFace(new int[] {4, 5, 7, 6}, "clip"); // clip top seg.addFace(new int[] {0, 6, 7, 1}); // bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } // there is no closing face in this case return seg; } if (y1t > wmax) { if (y1b >= wmin) { seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(x1, wmax, zf); seg.addVertex(x1, wmax, zb); seg.addVertex(x1, y1b, zf); seg.addVertex(x1, y1b, zb); seg.addFace(new int[] {0, 2, 4, 6, 8}); // front seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); // top seg.addFace(new int[] {4, 5, 7, 6}, "clip"); // clip top seg.addFace(new int[] {0, 8, 9, 1}); if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } if (closingFace) { seg.addFace(new int[] {6, 7, 9, 8}); } return seg; } else { return null; // this case should not be possible } } if (y1t > wmin) { if (y1b >= wmin) { // this is the regular segment, no clipping seg.addVertex(x1, y1t, zf); seg.addVertex(x1, y1t, zb); seg.addVertex(x1, y1b, zf); seg.addVertex(x1, y1b, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); // top seg.addFace(new int[] {0, 6, 7, 1}); // bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } if (closingFace) { seg.addFace(new int[] {4, 5, 7, 6}); } return seg; } else { seg.addVertex(x1, y1t, zf); seg.addVertex(x1, y1t, zb); seg.addVertex(x1, wmin, zf); seg.addVertex(x1, wmin, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6, 8}); // front seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); // top seg.addFace(new int[] {0, 8, 9, 1}); // bottom seg.addFace(new int[] {6, 7, 9, 8}, "clip"); // clip bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } if (closingFace) { seg.addFace(new int[] {4, 5, 7, 6}); } return seg; } } else { seg.addVertex(xpts[1], wmin, zf); seg.addVertex(xpts[1], wmin, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); // top seg.addFace(new int[] {0, 6, 7, 1}); // bottom seg.addFace(new int[] {4, 5, 7, 6}, "clip"); // clip bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } // there is no closing face in this case return seg; } } /** * Returns a segment for the case where the line height spans the lower * bound of the axis range at the left side of the chart. * * @param x0 * @param x1 * @param xpts * @param y0b * @param y0t * @param y1b * @param y1t * @param wmin * @param wmax * @param zf * @param zb * @param color * @param clipColor * @param openingFace * @param closingFace * * @return The segment. */ private Object3D createSegmentE(double x0, double x1, double[] xpts, double y0b, double y0t, double y1b, double y1t, double wmin, double wmax, double zf, double zb, Color color, Color clipColor, boolean openingFace, boolean closingFace) { if (y1b > wmax) { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, wmin, zf); seg.addVertex(x0, wmin, zb); seg.addVertex(x0, y0t, zf); seg.addVertex(x0, y0t, zb); seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(xpts[2], wmax, zf); seg.addVertex(xpts[2], wmax, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6, 8}); // front seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); // top seg.addFace(new int[] {4, 5, 7, 6}, "clip"); // clip top seg.addFace(new int[] {6, 7, 9, 8}); // bottom seg.addFace(new int[] {0, 8, 9, 1}, "clip"); // clip bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } return seg; } if (y1t > wmax) { if (y1b >= wmin) { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, wmin, zf); seg.addVertex(x0, wmin, zb); seg.addVertex(x0, y0t, zf); seg.addVertex(x0, y0t, zb); seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(x1, wmax, zf); seg.addVertex(x1, wmax, zb); seg.addVertex(x1, y1b, zf); seg.addVertex(x1, y1b, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6, 8, 10}); // front seg.addFace(new int[] {1, 11, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); // top seg.addFace(new int[] {5, 7, 6, 4}, "clip"); // clip top seg.addFace(new int[] {8, 9, 11, 10}); // bottom seg.addFace(new int[] {1, 0, 10, 11}, "clip"); if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } if (closingFace) { seg.addFace(new int[] {6, 7, 9, 8}); } return seg; } else { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, wmin, zf); seg.addVertex(x0, wmin, zb); seg.addVertex(x0, y0t, zf); seg.addVertex(x0, y0t, zb); seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(x1, wmax, zf); seg.addVertex(x1, wmax, zb); seg.addVertex(x1, wmin, zf); seg.addVertex(x1, wmin, zb); seg.addFace(new int[] {0, 2, 4, 6, 8}); // front seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); // top seg.addFace(new int[] {5, 7, 6, 4}, "clip"); // clip top seg.addFace(new int[] {0, 8, 9, 1}, "clip"); // clip bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } if (closingFace) { seg.addFace(new int[] {6, 7, 9, 8}); } return seg; } } if (y1t > wmin) { if (y1b >= wmin) { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, wmin, zf); seg.addVertex(x0, wmin, zb); seg.addVertex(x0, y0t, zf); seg.addVertex(x0, y0t, zb); seg.addVertex(x1, y1t, zf); seg.addVertex(x1, y1t, zb); seg.addVertex(x1, y1b, zf); seg.addVertex(x1, y1b, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6, 8}); // front seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); // top seg.addFace(new int[] {6, 7, 9, 8}); // bottom seg.addFace(new int[] {0, 8, 9, 1}, "clip"); // clip bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } if (closingFace) { seg.addFace(new int[] {4, 5, 7, 6}); } return seg; } else { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, wmin, zf); seg.addVertex(x0, wmin, zb); seg.addVertex(x0, y0t, zf); seg.addVertex(x0, y0t, zb); seg.addVertex(x1, y1t, zf); seg.addVertex(x1, y1t, zb); seg.addVertex(x1, wmin, zf); seg.addVertex(x1, wmin, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); // top seg.addFace(new int[] {0, 6, 7, 1}, "clip"); // clip bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } if (closingFace) { seg.addFace(new int[] {4, 5, 7, 6}); } return seg; } } Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(x0, wmin, zf); seg.addVertex(x0, wmin, zb); seg.addVertex(x0, y0t, zf); seg.addVertex(x0, y0t, zb); seg.addVertex(xpts[1], wmin, zf); seg.addVertex(xpts[1], wmin, zb); seg.addFace(new int[] {0, 2, 4}); // front seg.addFace(new int[] {1, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); // top seg.addFace(new int[] {0, 4, 5, 1}, "clip"); // clip bottom if (openingFace) { seg.addFace(new int[] {0, 1, 3, 2}); } // there is no closing face in this case return seg; } /** * Creates and returns a segment for the case where the line is completely * below the axis range at the left side. * * @param x0 * @param x1 * @param xpts * @param y0b * @param y0t * @param y1b * @param y1t * @param wmin * @param wmax * @param zf * @param zb * @param color * @param clipColor * @param openingFace ignored because there is no opening face in this * case. * @param closingFace * * @return A segment. */ private Object3D createSegmentF(double x0, double x1, double[] xpts, double y0b, double y0t, double y1b, double y1t, double wmin, double wmax, double zf, double zb, Color color, Color clipColor, boolean openingFace, boolean closingFace) { if (y1b > wmax) { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(xpts[1], wmin, zf); seg.addVertex(xpts[1], wmin, zb); seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(xpts[2], wmax, zf); seg.addVertex(xpts[2], wmax, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {0, 1, 3, 2}); // top seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top seg.addFace(new int[] {4, 5, 7, 6}); // bottom seg.addFace(new int[] {0, 6, 7, 1}, "clip"); // clip bottom // there are no opening and closing faces for this case return seg; } if (y1t > wmax) { if (y1b > wmin) { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(xpts[1], wmin, zf); seg.addVertex(xpts[1], wmin, zb); seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(x1, wmax, zf); seg.addVertex(x1, wmax, zb); seg.addVertex(x1, y1b, zf); seg.addVertex(x1, y1b, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6, 8}); // front seg.addFace(new int[] {1, 9, 7, 5, 3}); // rear seg.addFace(new int[] {2, 3, 5, 4}); //clip top seg.addFace(new int[] {0, 1, 3, 2}); // top seg.addFace(new int[] {0, 8, 9, 1}, "clip"); // clip bottom seg.addFace(new int[] {6, 7, 9, 8}); // bottom // there is no opening face in this case if (closingFace) { seg.addFace(new int[] {4, 5, 7, 6}); } return seg; } else { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(xpts[1], wmin, zf); seg.addVertex(xpts[1], wmin, zb); seg.addVertex(xpts[3], wmax, zf); seg.addVertex(xpts[3], wmax, zb); seg.addVertex(x1, wmax, zf); seg.addVertex(x1, wmax, zb); seg.addVertex(x1, wmin, zf); seg.addVertex(x1, wmin, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {0, 1, 3, 2}); // top seg.addFace(new int[] {2, 3, 5, 4}, "clip"); // clip top seg.addFace(new int[] {6, 7, 1, 0}, "clip"); // clip bottom if (closingFace) { seg.addFace(new int[] {4, 5, 7, 6}); } return seg; } } if (y1t > wmin) { if (y1b >= wmin) { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(xpts[1], wmin, zf); seg.addVertex(xpts[1], wmin, zb); seg.addVertex(x1, y1t, zf); seg.addVertex(x1, y1t, zb); seg.addVertex(x1, y1b, zf); seg.addVertex(x1, y1b, zb); seg.addVertex(xpts[0], wmin, zf); seg.addVertex(xpts[0], wmin, zb); seg.addFace(new int[] {0, 2, 4, 6}); // front seg.addFace(new int[] {1, 7, 5, 3}); // rear seg.addFace(new int[] {0, 1, 3, 2}); // top seg.addFace(new int[] {4, 5, 7, 6}); // bottom seg.addFace(new int[] {0, 6, 7, 1}, "clip"); // clip bottom if (closingFace) { seg.addFace(new int[] {2, 3, 5, 4}); } return seg; } else { Object3D seg = new Object3D(color, true); seg.setProperty(Object3D.COLOR_PREFIX + "clip", clipColor); seg.addVertex(xpts[1], wmin, zf); seg.addVertex(xpts[1], wmin, zb); seg.addVertex(x1, y1t, zf); seg.addVertex(x1, y1t, zb); seg.addVertex(x1, wmin, zf); seg.addVertex(x1, wmin, zb); seg.addFace(new int[] {0, 2, 4}); // front seg.addFace(new int[] {1, 5, 3}); // rear seg.addFace(new int[] {0, 1, 3, 2}); // top seg.addFace(new int[] {0, 4, 5, 1}, "clip"); // clip bottom // there is no opening face in this case if (closingFace) { seg.addFace(new int[] {2, 3, 5, 4}); } return seg; } } return null; // nothing to see } /** * Tests this renderer for equality with an arbitrary object. * * @param obj the object ({@code null} not permitted). * * @return A boolean. */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof LineRenderer3D)) { return false; } LineRenderer3D that = (LineRenderer3D) obj; if (this.lineWidth != that.lineWidth) { return false; } if (this.lineHeight != that.lineHeight) { return false; } if (this.isolatedItemWidthPercent != that.isolatedItemWidthPercent) { return false; } if (!ObjectUtils.equals(this.clipColorSource, that.clipColorSource)) { return false; } return super.equals(obj); } }