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}