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 java.awt.Shape;
036import java.awt.geom.Path2D;
037import java.awt.geom.Point2D;
038import java.awt.geom.Rectangle2D;
039import java.util.List;
040import java.util.ArrayList;
041
042/**
043 * Rendering info returned from the {@link Drawable3D} {@code draw()} 
044 * method.
045 * 
046 * @since 1.3
047 */
048public class RenderingInfo {
049    
050    /**
051     * A list of the faces drawn in order of rendering.
052     */
053    private final List<Face> faces;
054    
055    /** The projected points for the vertices in the faces. */
056    Point2D[] projPts;
057    
058    /** The x-translation. */
059    private final double dx;
060    
061    /** The y-translation. */
062    public double dy;
063    
064    /** 
065     * Storage for rendered elements in the model other than the 3D objects
066     * (caters for code that overlays other items such as labels).
067     */
068    List<RenderedElement> otherElements;
069
070    List<RenderedElement> otherOffsetElements;
071    
072    /**
073     * Creates a new instance.
074     * 
075     * @param faces  the rendered faces (in order of rendering).
076     * @param projPts  the projected points for all vertices in the 3D model.
077     * @param dx  the x-delta.
078     * @param dy  the y-delta.
079     */
080    public RenderingInfo(List<Face> faces, Point2D[] projPts, double dx, 
081            double dy) {
082        this.faces = faces;
083        this.projPts = projPts;
084        this.dx = dx;
085        this.dy = dy;
086        this.otherElements = new ArrayList<>();
087        this.otherOffsetElements = new ArrayList<>();
088    }
089    
090    /**
091     * Returns the list of faces rendered.
092     * 
093     * @return The list of faces.
094     */
095    public List<Face> getFaces() {
096        return this.faces;
097    }
098    
099    /**
100     * Returns the projected points.
101     * 
102     * @return The projected points. 
103     */
104    public Point2D[] getProjectedPoints() {
105        return this.projPts;
106    }
107    
108    /**
109     * Returns the x-translation amount.  All projected points are centered
110     * on (0, 0) but the rendering to the screen (or other Graphics2D target)
111     * performs two translations: the first is to the center of the bounding
112     * rectangle, and the second is to apply the translate2D attribute of the
113     * chart.  The result of these translations is stored here and used in the
114     * fetchObjectAt(x, y) method.
115     * 
116     * @return The x-translation. 
117     */
118    public double getDX() {
119        return this.dx;
120    }
121    
122    /**
123     * Returns the y-translation amount.
124     * 
125     * @return The y-translation. 
126     */
127    public double getDY() {
128        return this.dy;
129    }
130    
131    /**
132     * Adds a rendered element to the rendering info.
133     * 
134     * @param element  the element ({@code null} not permitted). 
135     */
136    public void addElement(RenderedElement element) {
137        this.otherElements.add(element);
138    }
139    
140    /**
141     * Adds a rendered element to the list of offset elements.
142     * 
143     * @param element  the element ({@code null} not permitted). 
144     */
145    public void addOffsetElement(RenderedElement element) {
146        this.otherOffsetElements.add(element);
147    }
148    
149    /**
150     * Fetches the object, if any, that is rendered at {@code (x, y)}.
151     * 
152     * @param x  the x-coordinate.
153     * @param y  the y-coordinate.
154     * 
155     * @return The object (or {@code null}). 
156     */
157    public Object3D fetchObjectAt(double x, double y) {
158        for (int i = this.faces.size() - 1; i >= 0; i--) {
159            Face f = this.faces.get(i);
160            if (f instanceof LabelFace) {
161                Rectangle2D bounds 
162                        = (Rectangle2D) f.getOwner().getProperty("labelBounds");
163                if (bounds != null && bounds.contains(x - dx, y - dy)) {
164                    return f.getOwner();
165                }
166            } else {
167                Path2D p = f.createPath(this.projPts);
168                if (p.contains(x - dx, y - dy)) {
169                    return f.getOwner();
170                }
171            }
172        }
173        return null;
174    }
175    
176    /**
177     * Finds the rendered element, if any, at the location {@code (x, y)}.
178     * The method first calls fetchObjectAt(x, y) to see if there is an
179     * object at the specified location and, if there is, returns a new
180     * RenderedElement instance for that object.  Otherwise, it searches the
181     * otherElements list to see if there is some other element (such as a
182     * title, legend, axis label or axis tick label) and returns that item.
183     * Finally, if no element is found, the method returns {@code null}.
184     * 
185     * @param x  the x-coordinate.
186     * @param y  the y-coordinate.
187     * 
188     * @return The interactive element or {@code null}.
189     */
190    public RenderedElement findElementAt(double x, double y) {
191        for (int i = this.otherElements.size() - 1; i >= 0; i--) {
192            RenderedElement element = this.otherElements.get(i);
193            Shape bounds = (Shape) element.getProperty(RenderedElement.BOUNDS);
194            if (bounds.contains(x, y)) {
195                return element;
196            }
197        }
198        
199        for (int i = this.otherOffsetElements.size() - 1; i >= 0; i--) {
200            RenderedElement element = this.otherOffsetElements.get(i);
201            Shape bounds = (Shape) element.getProperty(RenderedElement.BOUNDS);
202            if (bounds != null && bounds.contains(x - dx, y - dy)) {
203                return element;
204            }
205        }
206
207        Object3D obj = fetchObjectAt(x, y);
208        if (obj != null) {
209            RenderedElement element = new RenderedElement("obj3d", null);
210            element.setProperty(Object3D.ITEM_KEY, 
211                    obj.getProperty(Object3D.ITEM_KEY));
212            if (obj.getProperty(Object3D.CLASS_KEY) != null) {
213                element.setProperty(Object3D.CLASS_KEY, 
214                        obj.getProperty(Object3D.CLASS_KEY));
215            }
216            return element;
217        }
218        return null;
219    }
220    
221}