9fc0c9482a6efe1799138da022454e3c3b0300b5
[scilab.git] / scilab / modules / gui / src / java / org / scilab / modules / gui / editor / EntityPicker.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2012 - Pedro Arthur dos S. Souza
4  * Copyright (C) 2012 - Caio Lucas dos S. Souza
5  *
6  * This file must be used under the terms of the CeCILL.
7  * This source file is licensed as described in the file COPYING, which
8  * you should have received as part of this distribution.  The terms
9  * are also available at
10  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
11  *
12  */
13
14 package org.scilab.modules.gui.editor;
15
16
17 import org.scilab.modules.graphic_objects.graphicController.GraphicController;
18 import org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties;
19 import org.scilab.modules.graphic_objects.CallGraphicController;
20 import org.scilab.modules.renderer.CallRenderer;
21
22 import org.scilab.modules.graphic_objects.axes.Axes;
23 import org.scilab.modules.renderer.JoGLView.axes.AxesDrawer;
24 import org.scilab.modules.renderer.JoGLView.DrawerVisitor;
25 import org.scilab.forge.scirenderer.tranformations.Vector3d;
26
27 import org.scilab.modules.graphic_objects.PolylineData;
28 import org.scilab.modules.graphic_objects.SurfaceData;
29 import org.scilab.modules.gui.editor.CommonHandler;
30 import org.scilab.modules.gui.editor.ObjectSearcher;
31
32 import java.lang.Math;
33
34
35 /**
36 * Given a (x, y) window coord checks
37 * if it is closer or belongs to a polyline.
38 *
39 * @author Caio Souza <caioc2bolado@gmail.com>
40 * @author Pedro Souza <bygrandao@gmail.com>
41 *
42 * @since 2012-06-01
43 */
44
45
46 public class EntityPicker {
47
48     private Double dy;
49     private Double dx;
50     private boolean needTransform = false;
51     private Axes curAxes = null;
52     private final Double selectionDelta = 7.0;
53
54     /**
55     * Picks a polyline at the given position.
56     *
57     * @param figureUid     Figure uid to be check.
58     * @param posX         Position on x axis in pixels.
59     * @param posY         Position on y axis in pixels.
60     * @return            Picked polyline uid or null if there isn't any polyline at the given position.
61     */
62     public String pick(String figureUid, Integer posX, Integer posY) {
63
64         Integer[] position = {posX, posY};
65         String axes = AxesHandler.clickedAxes(figureUid, position);
66         if (axes == null) {
67             return null;
68         }
69
70         curAxes = AxesHandler.getAxesFromUid(axes);
71
72         double[] pos = {1.0 * posX, 1.0 * posY, 1.0};
73         double[] c2d = CallRenderer.get2dViewFromPixelCoordinates(axes, pos);
74
75         pos[0] += selectionDelta;
76         pos[1] += selectionDelta;
77         double[] c2d2 = CallRenderer.get2dViewFromPixelCoordinates(axes, pos);
78
79         dx = Math.abs(c2d[0] - c2d2[0]);
80         dy = Math.abs(c2d[1] - c2d2[1]);
81
82         needTransform = !isInDefaultView(curAxes);
83
84         /* Checks if the click is outside canvas drawable area*/
85         if (AxesHandler.isZoomBoxEnabled(axes)) {
86             if (!AxesHandler.isInZoomBoxBounds(axes, c2d[0], c2d[1])) {
87                 return null;
88             }
89         }
90
91         String polylines[] = (new ObjectSearcher()).search(axes, GraphicObjectProperties.__GO_POLYLINE__);
92
93         if (polylines != null) {
94             for (int i = 0; i < polylines.length; ++i) {
95                 if (CommonHandler.isVisible(polylines[i])) {
96
97                     if (CommonHandler.isLineEnabled(polylines[i])) {
98                         if (isOverLine(polylines[i], c2d[0], c2d[1]) != -1) {
99                             return polylines[i];
100                         }
101                     }
102                     if (CommonHandler.isMarkEnabled(polylines[i])) {
103                         if (isOverMark(polylines[i], c2d[0], c2d[1]) != -1) {
104                             return polylines[i];
105                         }
106                     }
107                 }
108             }
109         }
110
111         return null;
112     }
113
114     /**
115     * Check algorithm linear interpolation for each pair of points.
116     * @param uid    Polyline uid to be checked.
117     * @param x        position on x axis in view coordinates.
118     * @param y        position on y axis in view coordinates.
119     * @return        true if x,y belongs or is closest to the polyline.
120     */
121     private int isOverLine(String uid, Double x, Double y) {
122
123         double[] datax = (double[])PolylineData.getDataX(uid);
124         double[] datay = (double[])PolylineData.getDataY(uid);
125         double[] dataz = (double[])PolylineData.getDataZ(uid);
126         int size = datax.length;
127
128         if (PolylineData.isXShiftSet(uid) != 0) {
129             double[] x_shift = (double[])PolylineData.getShiftX(uid);
130             for (int i = 0; i < size; ++i) {
131                 datax[i] += x_shift[i];
132             }
133         }
134
135         if (PolylineData.isYShiftSet(uid) != 0) {
136             double[] y_shift = (double[])PolylineData.getShiftY(uid);
137             for (int i = 0; i < size; ++i) {
138                 datay[i] += y_shift[i];
139             }
140         }
141
142         if (PolylineData.isZShiftSet(uid) != 0) {
143             double[] z_shift = (double[])PolylineData.getShiftZ(uid);
144             for (int i = 0; i < size; ++i) {
145                 dataz[i] += z_shift[i];
146             }
147         }
148         datax = CommonHandler.toLogScale(datax, curAxes.getXAxisLogFlag());
149         datay = CommonHandler.toLogScale(datay, curAxes.getYAxisLogFlag());
150         dataz = CommonHandler.toLogScale(dataz, curAxes.getZAxisLogFlag());
151
152         double[] oldPoint = null;
153         if (needTransform) {
154             oldPoint = transformPoint(curAxes, datax[0], datay[0], dataz[0]);
155         }
156
157         for (Integer i = 0; i < (size - 1); ++i) {
158             if (needTransform) {
159                 double[] newPoint = transformPoint(curAxes, datax[i + 1], datay[i + 1], dataz[i + 1]);
160                 if (isInRange(oldPoint[0], newPoint[0], oldPoint[1], newPoint[1], x, y)) {
161                     return i;
162                 }
163                 oldPoint = newPoint;
164             } else {
165                 if (isInRange(datax[i], datax[i + 1], datay[i], datay[i + 1], x, y)) {
166                     return i;
167                 }
168             }
169         }
170         return -1;
171     }
172
173     private boolean isInRange(Double x0, Double x1, Double y0, Double y1, Double x, Double y) {
174         /* Fast bound check*/
175         double m = (x1 + x0) / 2;
176         double dx = m - x0;
177
178         double ca = (y1 - y0) / (x1 - x0);
179
180         double yy = y0 + ca * (x - x0);
181
182         double pix = this.dx / selectionDelta;
183         double m_y = (y1 + y0) / 2;
184         double dy = m_y - y0;
185
186         boolean ca_inf = (Math.abs(x1 - x0) < Math.abs(pix * 2));
187         boolean in_bounds = (Math.abs(m - x) <= Math.abs(pix * 2)) && (Math.abs(m_y - y) <= Math.abs(dy));
188
189         /*
190          * test if (x, y) belongs or is closer to the line
191          * if the angular coeficent -> inf(ca_inf), the interpolation fails
192          * then we use "in_bunds" test.
193          */
194         return (Math.abs(m - x) <= Math.abs(dx)) && (y >= (yy - this.dy)) && (y <= (yy + this.dy)) || (ca_inf && in_bounds);
195     }
196
197     /**
198      * Checks if the given point belongs the polyline mark.
199      * @param uid    Polyline uid to be checked.
200      * @param x        position on x axis in view coordinates.
201      * @param y        position on y axis in view coordinates.
202      * @return        True if x,y belongs to the polyline mark.
203      */
204     private int isOverMark(String uid, Double x, Double y) {
205
206         double[] datax = (double[])PolylineData.getDataX(uid);
207         double[] datay = (double[])PolylineData.getDataY(uid);
208         double[] dataz = (double[])PolylineData.getDataZ(uid);
209
210         if (PolylineData.isXShiftSet(uid) != 0) {
211             double[] x_shift = (double[])PolylineData.getShiftX(uid);
212             for (int i = 0; i < datax.length; ++i) {
213                 datax[i] += x_shift[i];
214             }
215         }
216
217         if (PolylineData.isYShiftSet(uid) != 0) {
218             double[] y_shift = (double[])PolylineData.getShiftY(uid);
219             for (int i = 0; i < datay.length; ++i) {
220                 datay[i] += y_shift[i];
221             }
222         }
223
224         if (PolylineData.isZShiftSet(uid) != 0) {
225             double[] z_shift = (double[])PolylineData.getShiftZ(uid);
226             for (int i = 0; i < dataz.length; ++i) {
227                 dataz[i] += z_shift[i];
228             }
229         }
230
231         datax = CommonHandler.toLogScale(datax, curAxes.getXAxisLogFlag());
232         datay = CommonHandler.toLogScale(datay, curAxes.getYAxisLogFlag());
233         dataz = CommonHandler.toLogScale(dataz, curAxes.getZAxisLogFlag());
234
235         Integer size = CommonHandler.getMarkSize(uid);
236         Integer unit = CommonHandler.getMarkSizeUnit(uid);
237
238         int finalSize = (unit == 1) ? (8 + 2 * size) : size;
239         finalSize /= 2;
240         double deltax = Math.abs((dx / selectionDelta) * finalSize);
241         double deltay = Math.abs((dy / selectionDelta) * finalSize);
242
243
244         for (int i = 0; i < datax.length; ++i) {
245             if (needTransform) {
246                 double[] point = transformPoint(curAxes, datax[i], datay[i], dataz[i]);
247                 if ((Math.abs(point[0] - x) <= deltax) && (Math.abs(point[1] - y) <= deltay)) {
248                     return i;
249                 }
250             } else {
251                 if ((Math.abs(datax[i] - x) <= deltax) && (Math.abs(datay[i] - y) <= deltay)) {
252                     return i;
253                 }
254             }
255         }
256         return -1;
257     }
258
259
260     public class PickedPoint {
261         public int point;
262         public boolean isSegment;
263         PickedPoint(int p, boolean segment) {
264             point = p;
265             isSegment = segment;
266         }
267     }
268
269     /**
270      * Given a polyline uid checks if the given point (px,py)
271      * belongs or is closer to any polyline point.
272      *
273      * @return The picked point or PickedPoint.point = -1 otherwise.
274      */
275     public PickedPoint pickPoint(String uid, int px, int py) {
276
277         PickedPoint point = new PickedPoint(-1, false);
278         Integer[] position = {px, py};
279         String figUid = (new ObjectSearcher()).searchParent(uid, GraphicObjectProperties.__GO_FIGURE__);
280         String axes = AxesHandler.clickedAxes(figUid, position);
281         if (axes == null) {
282             return point;
283         }
284
285         curAxes = AxesHandler.getAxesFromUid(axes);
286
287         double[] pos = {1.0 * px, 1.0 * py, 1.0};
288         double[] c2d = CallRenderer.get2dViewFromPixelCoordinates(axes, pos);
289
290         pos[0] += selectionDelta;
291         pos[1] += selectionDelta;
292         double[] c2d2 = CallRenderer.get2dViewFromPixelCoordinates(axes, pos);
293
294         dx = Math.abs(c2d[0] - c2d2[0]);
295         dy = Math.abs(c2d[1] - c2d2[1]);
296
297         needTransform = !isInDefaultView(curAxes);
298
299         /*try pick a point*/
300         point.point = isOverMark(uid, c2d[0], c2d[1]);
301         if (point.point != -1) {
302             return point;
303         } else {
304             /*try pick a segment*/
305             point.point = isOverLine(uid, c2d[0], c2d[1]);
306             point.isSegment = true;
307             return point;
308         }
309     }
310
311     /**
312      * Checks if the axes is in default view (2d view).
313      *
314      * @return true if is in default view, false otherwise.
315      */
316     private boolean isInDefaultView(Axes axes) {
317         Double rot[] = axes.getRotationAngles();
318         return (rot[0] == 0.0 && rot[1] == 270.0);
319     }
320
321     /**
322      * Project the given point in view plane.
323      *
324      * @return the projected point.
325      */
326     private double[] transformPoint(Axes axes, double x, double y, double z) {
327         double point[] = {x, y, z};
328         return AxesDrawer.compute2dViewCoordinates(axes, point);
329     }
330
331
332     public class LegendInfo {
333         public String legend = null;
334         public String polyline = null;
335         LegendInfo(String legend, String polyline) {
336             this.legend = legend;
337             this.polyline = polyline;
338         }
339     }
340
341     /**
342     * Check if the given position is over a legend object.
343     *
344     * @param axes The uid of axes that was clicked.
345     * @param position The mouse position (x, y).
346     * @return The LegendInfo if picked a legend null otherwise.
347     */
348     public LegendInfo pickLegend(String figure, Integer[] position) {
349
350         String axes = AxesHandler.clickedAxes(figure, position);
351         if (axes == null) {
352             return null;
353         }
354         String legend = LegendHandler.searchLegend(axes);
355         if (legend == null) {
356             return null;
357         }
358
359         String[] links;
360         Integer[] axesSize = {0, 0};
361         Double delta;
362         Double[] axesBounds = { 0., 0. }, dPosition = { 0., 0. }, legendPos = { 0., 0. }, legendBounds = { 0., 0., 0., 0. }, dimension = { 0., 0. };
363
364         axesSize = (Integer[])GraphicController.getController().getProperty(figure, GraphicObjectProperties.__GO_AXES_SIZE__);
365         axesBounds = (Double[])GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_AXES_BOUNDS__);
366         legendPos = (Double[])GraphicController.getController().getProperty(legend, GraphicObjectProperties.__GO_POSITION__);
367         links = (String[])GraphicController.getController().getProperty(legend, GraphicObjectProperties.__GO_LINKS__);
368         dPosition[0] = (position[0] - (axesBounds[0] * axesSize[0])) / (axesBounds[2] * axesSize[0]);
369         dPosition[1] = (position[1] - (axesBounds[1] * axesSize[1])) / (axesBounds[3] * axesSize[1]);
370         dimension = (Double[])GraphicController.getController().getProperty(legend, GraphicObjectProperties.__GO_SIZE__);
371         legendBounds[0] =  legendPos[0];
372         legendBounds[1] = legendPos[1];
373         legendBounds[2] = legendPos[0] + dimension[0];
374         legendBounds[3] = legendPos[1] + dimension[1];
375         delta = dimension[1] / (1.0 * links.length);
376
377         if (dPosition[0] >= legendBounds[0] && dPosition[0] <= legendBounds[2] && dPosition[1] >= legendBounds[1] && dPosition[1] <= legendBounds[3]) {
378             for (Integer i = 0; i < links.length; i++) {
379                 if (dPosition[1] >= (legendBounds[1] + i * delta) && dPosition[1] <= (legendBounds[1] + (i + 1) * delta)) {
380                     return new LegendInfo(legend, links[links.length - 1 - i]);
381                 }
382             }
383         }
384         return null;
385     }
386
387     /**
388      * Try pick an axis label at the given position.
389      * @param figure The figure uid.
390      * @param pos The position (x, y).
391      * @return The picked axis label or null.
392      */
393     public static AxesHandler.axisTo pickLabel(String figure, Integer[] pos) {
394
395         String axes = AxesHandler.clickedAxes(figure, pos);
396         if (axes == null) {
397             return null;
398         }
399         Double[] corners;
400         Double radAngle = 0.;
401         int rotate = 0;
402         double[] point = new double[3];
403         double[] coord;
404         String[] label = {  (String)GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_X_AXIS_LABEL__),
405                             (String)GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_Y_AXIS_LABEL__),
406                             (String)GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_Z_AXIS_LABEL__),
407                             (String)GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_TITLE__)
408                          };
409
410         boolean[] logScale = {  (Boolean)GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_X_AXIS_LOG_FLAG__),
411                                 (Boolean)GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_Y_AXIS_LOG_FLAG__)
412                              };
413
414         for (Integer i = 0; i < 4; i++) {
415             corners = (Double[])GraphicController.getController().getProperty(label[i], GraphicObjectProperties.__GO_CORNERS__);
416             radAngle = (Double)GraphicController.getController().getProperty(label[i], GraphicObjectProperties.__GO_FONT_ANGLE__);
417             rotate = ((int)((radAngle * 2) / Math.PI)) % 2;
418             if (rotate == 1) {
419                 corners = rotateCorners(corners);
420             }
421             point[0] = pos[0];
422             point[1] = pos[1];
423             point[2] = 1.0;
424             coord = CallRenderer.get2dViewFromPixelCoordinates(axes, point);
425
426             coord[0] = CommonHandler.InverseLogScale(coord[0], logScale[0]);
427             coord[1] = CommonHandler.InverseLogScale(coord[1], logScale[1]);
428
429             if ((coord[0] >= corners[0] && coord[0] <= corners[6]) || (coord[0] <= corners[0] && coord[0] >= corners[6])) {
430                 if ((coord[1] >= corners[1] && coord[1] <= corners[4]) || (coord[1] <= corners[1] && coord[1] >= corners[4])) {
431                     switch (i) {
432                         case 0:
433                             return AxesHandler.axisTo.__X__;
434                         case 1:
435                             return AxesHandler.axisTo.__Y__;
436                         case 2:
437                             return AxesHandler.axisTo.__Z__;
438                         case 3:
439                             return AxesHandler.axisTo.__TITLE__;
440                     }
441                 }
442             }
443         }
444         return null;
445     }
446
447     /**
448     * Rotate the Corners points 90 degrees
449     *
450     * @param vec The corners points vector(should be 4 points x 3 coord, arranged sequentially)
451     */
452     private static Double[] rotateCorners(Double[] vec) {
453
454         Integer length = vec.length;
455         Double[] newVec = new Double[length];
456         for (Integer i = 0; i < length - 3; i++) {
457             newVec[i + 3] = vec[i];
458         }
459         newVec[0] = vec[length - 3];
460         newVec[1] = vec[length - 2];
461         newVec[2] = vec[length - 1];
462         return newVec;
463     }
464
465     /*
466      * Given a figure search for all plot3d's and for each surface
467      * test if the ray given by the mouse position intersects any surface
468      * @param figure Figure unique identifier.
469      * @param pos Mouse position (x, y).
470      * @return The nearest surface intersected or null otherwise.
471      */
472     String pickSurface(String figure, Integer[] pos) {
473
474         String uid = AxesHandler.clickedAxes(figure, pos);
475         curAxes = (Axes)GraphicController.getController().getObjectFromId(uid);
476
477         double[] mat = DrawerVisitor.getVisitor(figure).getAxesDrawer().getProjection(uid).getMatrix();
478
479
480         Vector3d v0 = AxesDrawer.unProject(curAxes, new Vector3d(1.0f * pos[0], 1.0f * pos[1], 0.0));
481         Vector3d v1 = AxesDrawer.unProject(curAxes, new Vector3d(1.0f * pos[0], 1.0f * pos[1], 1.0));
482         Vector3d Dir = v0.minus(v1).getNormalized();
483
484
485         Integer[] types = {GraphicObjectProperties.__GO_PLOT3D__, GraphicObjectProperties.__GO_FAC3D__, GraphicObjectProperties.__GO_GRAYPLOT__};
486         String[] objs = (new ObjectSearcher()).searchMultiple(figure, types);
487         double Z = 2.0;
488         String picked = null;
489         if (objs != null) {
490             for (int i = 0; i < objs.length; ++i) {
491                 double curZ = SurfaceData.pickSurface(objs[i], v0.getX(), v0.getY(), v0.getZ(),
492                                                       Dir.getX(), Dir.getY(), Dir.getZ(), mat[2], mat[6], mat[10], mat[14]);
493                 if (curZ < Z) {
494                     picked = objs[i];
495                     Z = curZ;
496                 }
497             }
498         }
499
500         return picked;
501     }
502
503
504     public String pickDatatip(String figure, Integer[] pos) {
505
506         String axes = AxesHandler.clickedAxes(figure, pos);
507         if (axes == null) {
508             return null;
509         }
510
511         curAxes = AxesHandler.getAxesFromUid(axes);
512         String[] datatips = (new ObjectSearcher()).search(axes, GraphicObjectProperties.__GO_DATATIP__);
513         boolean[] logFlags = {  curAxes.getXAxisLogFlag(), curAxes.getYAxisLogFlag(), curAxes.getZAxisLogFlag()};
514
515         if (datatips != null) {
516
517             needTransform = !isInDefaultView(curAxes);
518             double[] pix_pos = {1.0 * pos[0], 1.0 * pos[1], 1.0};
519             double[] c2d = CallRenderer.get2dViewFromPixelCoordinates(axes, pix_pos);
520             pix_pos[0] += 1.0;
521             pix_pos[1] += 1.0;
522             double[] c2d2 = CallRenderer.get2dViewFromPixelCoordinates(axes, pix_pos);
523             double dx = Math.abs(c2d[0] - c2d2[0]);
524             double dy = Math.abs(c2d[1] - c2d2[1]);
525
526             for (int i = 0; i < datatips.length; ++i) {
527
528                 Double[] tip_pos = (Double[])GraphicController.getController().getProperty(datatips[i], GraphicObjectProperties.__GO_DATATIP_DATA__);
529                 double point[] = new double[3];
530                 point[0] = CommonHandler.logScale(tip_pos[0], logFlags[0]);
531                 point[1] = CommonHandler.logScale(tip_pos[1], logFlags[1]);
532                 point[2] = CommonHandler.logScale(tip_pos[2], logFlags[2]);
533
534
535                 if (needTransform) {
536                     point = transformPoint(curAxes, tip_pos[0], tip_pos[1], tip_pos[2]);
537                 }
538
539                 Integer size = CommonHandler.getMarkSize(datatips[i]);
540                 Integer unit = CommonHandler.getMarkSizeUnit(datatips[i]);
541
542                 int finalSize = (unit == 1) ? (8 + 2 * size) : size;
543                 finalSize /= 2;
544
545                 if ((Math.abs(point[0] - c2d[0]) <= dx * finalSize) && (Math.abs(point[1] - c2d[1]) <= dy * finalSize)) {
546                     return datatips[i];
547                 }
548
549             }
550         }
551         return null;
552     }
553 }
554