001/* =========================================================== 002 * Orson Charts : a 3D chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C)opyright 2013-2022, by David Gilbert. All rights reserved. 006 * 007 * https://github.com/jfree/orson-charts 008 * 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as published by 011 * the Free Software Foundation, either version 3 of the License, or 012 * (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public License 020 * along with this program. If not, see <http://www.gnu.org/licenses/>. 021 * 022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 023 * Other names may be trademarks of their respective owners.] 024 * 025 * If you do not wish to be bound by the terms of the GPL, an alternative 026 * commercial license can be purchased. For details, please see visit the 027 * Orson Charts home page: 028 * 029 * http://www.object-refinery.com/orsoncharts/index.html 030 * 031 */ 032 033package org.jfree.chart3d.graphics3d; 034 035import org.jfree.chart3d.internal.Args; 036 037/** 038 * A line segment in 3D space. 039 * 040 * @since 1.5 041 */ 042public class Line3D { 043 044 private Point3D start; 045 046 private Point3D end; 047 048 /** 049 * Creates a new line in 3D space. 050 * 051 * @param start the starting point ({@code null} not permitted). 052 * @param end the ending point ({@code null} not permitted). 053 */ 054 public Line3D(Point3D start, Point3D end) { 055 Args.nullNotPermitted(start, "start"); 056 Args.nullNotPermitted(end, "end"); 057 this.start = start; 058 this.end = end; 059 } 060 061 /** 062 * Creates a new line in 3D space between the points {@code (x0, y0, z0)} 063 * and {@code (x1, y1, z1)}. 064 * 065 * @param x0 the x-coordinate for the line's start point. 066 * @param y0 the y-coordinate for the line's start point. 067 * @param z0 the z-coordinate for the line's start point. 068 * @param x1 the x-coordinate for the line's end point. 069 * @param y1 the y-coordinate for the line's end point. 070 * @param z1 the z-coordinate for the line's end point. 071 */ 072 public Line3D(double x0, double y0, double z0, double x1, double y1, 073 double z1) { 074 this.start = new Point3D(x0, y0, z0); 075 this.end = new Point3D(x1, y1, z1); 076 } 077 078 /** 079 * Returns the starting point for the line. 080 * 081 * @return The starting point (never {@code null}). 082 */ 083 public Point3D getStart() { 084 return this.start; 085 } 086 087 /** 088 * Returns the ending point for the line. 089 * 090 * @return The ending point (never {@code null}). 091 */ 092 public Point3D getEnd() { 093 return this.end; 094 } 095 096 /** 097 * Calculates and returns the line segment that is the result of cropping 098 * the specified line segment to fit within an axis aligned bounding box. 099 * 100 * @param line the original line segment ({@code null} not permitted). 101 * @param x0 the lower x-bound. 102 * @param x1 the upper x-bound. 103 * @param y0 the lower y-bound. 104 * @param y1 the upper y-bound. 105 * @param z0 the lower z-bound. 106 * @param z1 the upper z-bound. 107 * 108 * @return The cropped line segment (or {@code null} if the original line 109 * segment falls outside the bounding box). 110 */ 111 public static Line3D cropLineToAxisAlignedBoundingBox(Line3D line, 112 double x0, double x1, double y0, double y1, double z0, double z1) { 113 114 // we need this method to be fast, since it will get called a lot of 115 // times during line chart rendering...there may be a faster method 116 // than what is implemented below, in which case we should use it 117 double xx0 = line.getStart().getX(); 118 double xx1 = line.getEnd().getX(); 119 int kx0 = 2; 120 if (xx0 < x0) { 121 kx0 = 1; 122 } else if (xx0 > x1) { 123 kx0 = 4; 124 } 125 int kx1 = 2; 126 if (xx1 < x0) { 127 kx1 = 1; 128 } else if (xx1 > x1) { 129 kx1 = 4; 130 } 131 if ((kx0 | kx1) == 1 || (kx0 | kx1) == 4) { 132 return null; 133 } 134 135 double yy0 = line.getStart().getY(); 136 double yy1 = line.getEnd().getY(); 137 int ky0 = 2; 138 if (yy0 < y0) { 139 ky0 = 1; 140 } else if (yy0 > y1) { 141 ky0 = 4; 142 } 143 int ky1 = 2; 144 if (yy1 < y0) { 145 ky1 = 1; 146 } else if (yy1 > y1) { 147 ky1 = 4; 148 } 149 if ((ky0 | ky1) == 1 || (ky0 | ky1) == 4) { 150 return null; 151 } 152 153 double zz0 = line.getStart().getZ(); 154 double zz1 = line.getEnd().getZ(); 155 int kz0 = 2; 156 if (zz0 < z0) { 157 kz0 = 1; 158 } else if (zz0 > z1) { 159 kz0 = 4; 160 } 161 int kz1 = 2; 162 if (zz1 < z0) { 163 kz1 = 1; 164 } else if (zz1 > z1) { 165 kz1 = 4; 166 } 167 if ((kz0 | kz1) == 1 || (kz0 | kz1) == 4) { 168 return null; 169 } 170 171 // if the X's, Y's and Z's are all inside, just return the line 172 if ((kx0 | kx1 | ky0 | ky1 | kz0 | kz1) == 2) { 173 return line; 174 } 175 176 // now we know we have to do some clipping 177 Point3D firstValidPoint = null; 178 if (isPoint3DInBoundingBox(line.getStart(), x0, x1, y0, y1, z0, z1)) { 179 firstValidPoint = line.getStart(); 180 } 181 if (isPoint3DInBoundingBox(line.getEnd(), x0, x1, y0, y1, z0, z1)) { 182 firstValidPoint = line.getEnd(); 183 } 184 if (((kx0 | kx1) & 1) != 0) { // X's span the lower bound 185 Point3D p = projectLineOnX(line, x0); 186 if (isPoint3DInBoundingBox(p, x0, x1, y0, y1, z0, z1)) { 187 if (firstValidPoint == null) { 188 firstValidPoint = p; 189 } else { 190 return new Line3D(firstValidPoint, p); 191 } 192 } 193 } 194 if (((kx0 | kx1) & 4) != 0) { // X's span the upper bound 195 Point3D p = projectLineOnX(line, x1); 196 if (isPoint3DInBoundingBox(p, x0, x1, y0, y1, z0, z1)) { 197 if (firstValidPoint == null) { 198 firstValidPoint = p; 199 } else { 200 return new Line3D(firstValidPoint, p); 201 } 202 } 203 } 204 205 if (((ky0 | ky1) & 1) != 0) { // Y's span the lower bound 206 Point3D p = projectLineOnY(line, y0); 207 if (isPoint3DInBoundingBox(p, x0, x1, y0, y1, z0, z1)) { 208 if (firstValidPoint == null) { 209 firstValidPoint = p; 210 } else { 211 return new Line3D(firstValidPoint, p); 212 } 213 } 214 } 215 if (((ky0 | ky1) & 4) != 0) { // Y's span the upper bound 216 Point3D p = projectLineOnY(line, y1); 217 if (isPoint3DInBoundingBox(p, x0, x1, y0, y1, z0, z1)) { 218 if (firstValidPoint == null) { 219 firstValidPoint = p; 220 } else { 221 return new Line3D(firstValidPoint, p); 222 } 223 } 224 } 225 if (((kz0 | kz1) & 1) != 0) { // X's span the lower bound 226 Point3D p = projectLineOnZ(line, z0); 227 if (isPoint3DInBoundingBox(p, x0, x1, y0, y1, z0, z1)) { 228 if (firstValidPoint == null) { 229 firstValidPoint = p; 230 } else { 231 return new Line3D(firstValidPoint, p); 232 } 233 } 234 } 235 if (((kz0 | kz1) & 4) != 0) { // X's span the upper bound 236 Point3D p = projectLineOnZ(line, z1); 237 if (isPoint3DInBoundingBox(p, x0, x1, y0, y1, z0, z1)) { 238 if (firstValidPoint == null) { 239 firstValidPoint = p; 240 } else { 241 return new Line3D(firstValidPoint, p); 242 } 243 } 244 } 245 return null; 246 } 247 248 private static Point3D projectLineOnX(Line3D line, double x) { 249 double x0 = line.getStart().getX(); 250 double x1 = line.getEnd().getX(); 251 double factor = 0.0; 252 if (Math.abs(x1 - x0) > 0) { 253 factor = (x - x0) / (x1 - x0); 254 } 255 if (factor >= 1.0) { 256 return line.getEnd(); 257 } else if (factor <= 0.0) { 258 return line.getStart(); 259 } else { 260 double y0 = line.getStart().getY(); 261 double y1 = line.getEnd().getY(); 262 double z0 = line.getStart().getZ(); 263 double z1 = line.getEnd().getZ(); 264 return new Point3D(x0 + factor * (x1 - x0), y0 + factor * (y1 - y0), 265 z0 + factor * (z1 - z0)); 266 } 267 } 268 269 private static Point3D projectLineOnY(Line3D line, double y) { 270 double y0 = line.getStart().getY(); 271 double y1 = line.getEnd().getY(); 272 double factor = 0.0; 273 if (Math.abs(y1 - y0) > 0) { 274 factor = (y - y0) / (y1 - y0); 275 } 276 if (factor >= 1.0) { 277 return line.getEnd(); 278 } else if (factor <= 0.0) { 279 return line.getStart(); 280 } else { 281 double x0 = line.getStart().getX(); 282 double x1 = line.getEnd().getX(); 283 double z0 = line.getStart().getZ(); 284 double z1 = line.getEnd().getZ(); 285 return new Point3D(x0 + factor * (x1 - x0), y0 + factor * (y1 - y0), 286 z0 + factor * (z1 - z0)); 287 } 288 } 289 290 private static Point3D projectLineOnZ(Line3D line, double z) { 291 double z0 = line.getStart().getZ(); 292 double z1 = line.getEnd().getZ(); 293 double factor = 0.0; 294 if (Math.abs(z1 - z0) > 0) { 295 factor = (z - z0) / (z1 - z0); 296 } 297 if (factor >= 1.0) { 298 return line.getEnd(); 299 } else if (factor <= 0.0) { 300 return line.getStart(); 301 } else { 302 double x0 = line.getStart().getX(); 303 double x1 = line.getEnd().getX(); 304 double y0 = line.getStart().getY(); 305 double y1 = line.getEnd().getY(); 306 return new Point3D(x0 + factor * (x1 - x0), y0 + factor * (y1 - y0), 307 z0 + factor * (z1 - z0)); 308 } 309 } 310 311 private static boolean isPoint3DInBoundingBox(Point3D p, double x0, 312 double x1, double y0, double y1, double z0, double z1) { 313 double x = p.getX(); 314 if (x < x0) { 315 return false; 316 } 317 if (x > x1) { 318 return false; 319 } 320 double y = p.getY(); 321 if (y < y0) { 322 return false; 323 } 324 if (y > y1) { 325 return false; 326 } 327 double z = p.getZ(); 328 if (z < z0) { 329 return false; 330 } 331 if (z > z1) { 332 return false; 333 } 334 return true; 335 } 336}