/* =========================================================== * 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 com.orsoncharts.util.ArgChecks; /** * A line segment in 3D space. * * @since 1.5 */ public class Line3D { private Point3D start; private Point3D end; /** * Creates a new line in 3D space. * * @param start the starting point ({@code null} not permitted). * @param end the ending point ({@code null} not permitted). */ public Line3D(Point3D start, Point3D end) { ArgChecks.nullNotPermitted(start, "start"); ArgChecks.nullNotPermitted(end, "end"); this.start = start; this.end = end; } /** * Creates a new line in 3D space between the points {@code (x0, y0, z0)} * and {@code (x1, y1, z1)}. * * @param x0 the x-coordinate for the line's start point. * @param y0 the y-coordinate for the line's start point. * @param z0 the z-coordinate for the line's start point. * @param x1 the x-coordinate for the line's end point. * @param y1 the y-coordinate for the line's end point. * @param z1 the z-coordinate for the line's end point. */ public Line3D(double x0, double y0, double z0, double x1, double y1, double z1) { this.start = new Point3D(x0, y0, z0); this.end = new Point3D(x1, y1, z1); } /** * Returns the starting point for the line. * * @return The starting point (never {@code null}). */ public Point3D getStart() { return this.start; } /** * Returns the ending point for the line. * * @return The ending point (never {@code null}). */ public Point3D getEnd() { return this.end; } /** * Calculates and returns the line segment that is the result of cropping * the specified line segment to fit within an axis aligned bounding box. * * @param line the original line segment ({@code null} not permitted). * @param x0 the lower x-bound. * @param x1 the upper x-bound. * @param y0 the lower y-bound. * @param y1 the upper y-bound. * @param z0 the lower z-bound. * @param z1 the upper z-bound. * * @return The cropped line segment (or {@code null} if the original line * segment falls outside the bounding box). */ public static Line3D cropLineToAxisAlignedBoundingBox(Line3D line, double x0, double x1, double y0, double y1, double z0, double z1) { // we need this method to be fast, since it will get called a lot of // times during line chart rendering...there may be a faster method // than what is implemented below, in which case we should use it double xx0 = line.getStart().getX(); double xx1 = line.getEnd().getX(); int kx0 = 2; if (xx0 < x0) { kx0 = 1; } else if (xx0 > x1) { kx0 = 4; } int kx1 = 2; if (xx1 < x0) { kx1 = 1; } else if (xx1 > x1) { kx1 = 4; } if ((kx0 | kx1) == 1 || (kx0 | kx1) == 4) { return null; } double yy0 = line.getStart().getY(); double yy1 = line.getEnd().getY(); int ky0 = 2; if (yy0 < y0) { ky0 = 1; } else if (yy0 > y1) { ky0 = 4; } int ky1 = 2; if (yy1 < y0) { ky1 = 1; } else if (yy1 > y1) { ky1 = 4; } if ((ky0 | ky1) == 1 || (ky0 | ky1) == 4) { return null; } double zz0 = line.getStart().getZ(); double zz1 = line.getEnd().getZ(); int kz0 = 2; if (zz0 < z0) { kz0 = 1; } else if (zz0 > z1) { kz0 = 4; } int kz1 = 2; if (zz1 < z0) { kz1 = 1; } else if (zz1 > z1) { kz1 = 4; } if ((kz0 | kz1) == 1 || (kz0 | kz1) == 4) { return null; } // if the X's, Y's and Z's are all inside, just return the line if ((kx0 | kx1 | ky0 | ky1 | kz0 | kz1) == 2) { return line; } // now we know we have to do some clipping Point3D firstValidPoint = null; if (isPoint3DInBoundingBox(line.getStart(), x0, x1, y0, y1, z0, z1)) { firstValidPoint = line.getStart(); } if (isPoint3DInBoundingBox(line.getEnd(), x0, x1, y0, y1, z0, z1)) { firstValidPoint = line.getEnd(); } if (((kx0 | kx1) & 1) != 0) { // X's span the lower bound Point3D p = projectLineOnX(line, x0); if (isPoint3DInBoundingBox(p, x0, x1, y0, y1, z0, z1)) { if (firstValidPoint == null) { firstValidPoint = p; } else { return new Line3D(firstValidPoint, p); } } } if (((kx0 | kx1) & 4) != 0) { // X's span the upper bound Point3D p = projectLineOnX(line, x1); if (isPoint3DInBoundingBox(p, x0, x1, y0, y1, z0, z1)) { if (firstValidPoint == null) { firstValidPoint = p; } else { return new Line3D(firstValidPoint, p); } } } if (((ky0 | ky1) & 1) != 0) { // Y's span the lower bound Point3D p = projectLineOnY(line, y0); if (isPoint3DInBoundingBox(p, x0, x1, y0, y1, z0, z1)) { if (firstValidPoint == null) { firstValidPoint = p; } else { return new Line3D(firstValidPoint, p); } } } if (((ky0 | ky1) & 4) != 0) { // Y's span the upper bound Point3D p = projectLineOnY(line, y1); if (isPoint3DInBoundingBox(p, x0, x1, y0, y1, z0, z1)) { if (firstValidPoint == null) { firstValidPoint = p; } else { return new Line3D(firstValidPoint, p); } } } if (((kz0 | kz1) & 1) != 0) { // X's span the lower bound Point3D p = projectLineOnZ(line, z0); if (isPoint3DInBoundingBox(p, x0, x1, y0, y1, z0, z1)) { if (firstValidPoint == null) { firstValidPoint = p; } else { return new Line3D(firstValidPoint, p); } } } if (((kz0 | kz1) & 4) != 0) { // X's span the upper bound Point3D p = projectLineOnZ(line, z1); if (isPoint3DInBoundingBox(p, x0, x1, y0, y1, z0, z1)) { if (firstValidPoint == null) { firstValidPoint = p; } else { return new Line3D(firstValidPoint, p); } } } return null; } private static Point3D projectLineOnX(Line3D line, double x) { double x0 = line.getStart().getX(); double x1 = line.getEnd().getX(); double factor = 0.0; if (Math.abs(x1 - x0) > 0) { factor = (x - x0) / (x1 - x0); } if (factor >= 1.0) { return line.getEnd(); } else if (factor <= 0.0) { return line.getStart(); } else { double y0 = line.getStart().getY(); double y1 = line.getEnd().getY(); double z0 = line.getStart().getZ(); double z1 = line.getEnd().getZ(); return new Point3D(x0 + factor * (x1 - x0), y0 + factor * (y1 - y0), z0 + factor * (z1 - z0)); } } private static Point3D projectLineOnY(Line3D line, double y) { double y0 = line.getStart().getY(); double y1 = line.getEnd().getY(); double factor = 0.0; if (Math.abs(y1 - y0) > 0) { factor = (y - y0) / (y1 - y0); } if (factor >= 1.0) { return line.getEnd(); } else if (factor <= 0.0) { return line.getStart(); } else { double x0 = line.getStart().getX(); double x1 = line.getEnd().getX(); double z0 = line.getStart().getZ(); double z1 = line.getEnd().getZ(); return new Point3D(x0 + factor * (x1 - x0), y0 + factor * (y1 - y0), z0 + factor * (z1 - z0)); } } private static Point3D projectLineOnZ(Line3D line, double z) { double z0 = line.getStart().getZ(); double z1 = line.getEnd().getZ(); double factor = 0.0; if (Math.abs(z1 - z0) > 0) { factor = (z - z0) / (z1 - z0); } if (factor >= 1.0) { return line.getEnd(); } else if (factor <= 0.0) { return line.getStart(); } else { double x0 = line.getStart().getX(); double x1 = line.getEnd().getX(); double y0 = line.getStart().getY(); double y1 = line.getEnd().getY(); return new Point3D(x0 + factor * (x1 - x0), y0 + factor * (y1 - y0), z0 + factor * (z1 - z0)); } } private static boolean isPoint3DInBoundingBox(Point3D p, double x0, double x1, double y0, double y1, double z0, double z1) { double x = p.getX(); if (x < x0) { return false; } if (x > x1) { return false; } double y = p.getY(); if (y < y0) { return false; } if (y > y1) { return false; } double z = p.getZ(); if (z < z0) { return false; } if (z > z1) { return false; } return true; } }