[renderer] mouseWheel zoom now centered on pointer location
[scilab.git] / scilab / modules / renderer / src / java / org / scilab / modules / renderer / utils / 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  * Copyright (C) 2014 - Scilab Enterprises - Calixte DENIZET
6  *
7  * Copyright (C) 2012 - 2016 - Scilab Enterprises
8  *
9  * This file is hereby licensed under the terms of the GNU GPL v2.0,
10  * pursuant to article 5.3.4 of the CeCILL v.2.1.
11  * This file was originally licensed under the terms of the CeCILL v2.1,
12  * and continues to be available under such terms.
13  * For more information, see the COPYING file which you should have received
14  * along with this program.
15  *
16  */
17
18 package org.scilab.modules.renderer.utils;
19
20 import org.scilab.modules.graphic_objects.graphicController.GraphicController;
21 import org.scilab.modules.graphic_objects.graphicObject.GraphicObjectProperties;
22 import org.scilab.modules.renderer.CallRenderer;
23
24 import org.scilab.modules.graphic_objects.axes.Axes;
25 import org.scilab.modules.graphic_objects.axes.AxesContainer;
26 import org.scilab.modules.renderer.JoGLView.axes.AxesDrawer;
27 import org.scilab.forge.scirenderer.tranformations.Vector3d;
28
29 import org.scilab.modules.graphic_objects.PolylineData;
30 import org.scilab.modules.graphic_objects.SurfaceData;
31 import org.scilab.modules.renderer.JoGLView.DrawerVisitor;
32
33 /**
34  * Given a (x, y) window coord checks
35  * if it is closer or belongs to a polyline.
36  *
37  * @author Caio Souza <caioc2bolado@gmail.com>
38  * @author Pedro Souza <bygrandao@gmail.com>
39  *
40  * @since 2012-06-01
41  */
42
43
44 public class EntityPicker {
45
46     private static final int EPS = 2;
47
48     /**
49      * Picks a polyline at the given position.
50      *
51      * @param figureUid     Figure uid to be check.
52      * @param posX         Position on x axis in pixels.
53      * @param posY         Position on y axis in pixels.
54      * @return            Picked polyline uid or null if there isn't any polyline at the given position.
55      */
56     public Integer pick(Integer figureUid, Integer posX, Integer posY) {
57         Integer[] position = {posX, posY};
58         Integer axes = AxesHandler.clickedAxes(figureUid, position);
59         if (axes == null) {
60             return null;
61         }
62
63         double[] pos = {posX.doubleValue(), posY.doubleValue(), 1.0};
64         double[] c2d = CallRenderer.get2dViewFromPixelCoordinates(axes, pos);
65
66         /* Checks if the click is outside canvas drawable area*/
67         if (AxesHandler.isZoomBoxEnabled(axes) && !AxesHandler.isInZoomBoxBounds(axes, c2d[0], c2d[1])) {
68             return null;
69         }
70
71         Integer polylines[] = (new ObjectSearcher()).search(axes, GraphicObjectProperties.__GO_POLYLINE__);
72         if (polylines != null) {
73             for (int i = 0; i < polylines.length; ++i) {
74                 if (CommonHandler.isVisible(polylines[i])) {
75                     if (CommonHandler.isLineEnabled(polylines[i])) {
76                         if (isOverLine(axes, polylines[i], posX, posY) != -1) {
77                             return polylines[i];
78                         }
79                     }
80
81                     if (CommonHandler.isMarkEnabled(polylines[i])) {
82                         if (isOverMark(axes, polylines[i], posX, posY) != -1) {
83                             return polylines[i];
84                         }
85                     }
86
87                     if (!CommonHandler.isLineEnabled(polylines[i])) {
88                         if (isOverDot(axes, polylines[i], posX, posY) != -1) {
89                             return polylines[i];
90                         }
91                     }
92                 }
93             }
94         }
95
96         return null;
97     }
98
99     public static DatatipCommon.Segment getSegment(Integer uid, int index) {
100         double[] datax = (double[])PolylineData.getDataX(uid);
101         double[] datay = (double[])PolylineData.getDataY(uid);
102         double[] dataz = (double[])PolylineData.getDataZ(uid);
103
104         if (datax.length == 1) {
105             return new DatatipCommon.Segment(0, datax[0], datax[0], datay[0], datay[0], dataz[0], dataz[0]);
106         }
107
108         if (index <= 0) {
109             return new DatatipCommon.Segment(0, datax[0], datax[1], datay[0], datay[1], dataz[0], dataz[1]);
110         } else if (index >= datax.length - 1) {
111             return new DatatipCommon.Segment(datax.length - 2, datax[datax.length - 2], datax[datax.length - 1], datay[datay.length - 2], datay[datay.length - 1], dataz[dataz.length - 2], dataz[dataz.length - 1]);
112         } else {
113             return new DatatipCommon.Segment(index, datax[index], datax[index + 1], datay[index], datay[index + 1], datay[index], datay[index + 1]);
114         }
115     }
116
117     public static double[] getSegment(Integer figureUid, Integer uid, int index, int x, int y) {
118         final Integer axes = AxesHandler.clickedAxes(figureUid, new Integer[] {x, y});
119         final double[][] pixs = EntityPicker.getPixelsData(axes, uid);
120         final double[] p1 = pixs[index];
121         final double[] p2 = pixs[index + 1];
122         final double px = p2[0] - p1[0];
123         final double py = p2[1] - p1[1];
124         final double ps = (x - p1[0]) * px + (y - p1[1]) * py;
125         final double[] info = new double[] {index, 0};
126
127         if (0 <= ps) {
128             final double n2 = px * px + py * py;
129             // the projected point is in the segment [p1,p2]
130             if (ps <= n2) {
131                 info[1] = ps / n2;
132             } else {
133                 info[1] = 1;
134             }
135         }
136
137         return info;
138     }
139
140     public static double[][] getPixelsData(Integer axes, Integer uid) {
141         double[] datax = (double[])PolylineData.getDataX(uid);
142         double[] datay = (double[])PolylineData.getDataY(uid);
143         double[] dataz = (double[])PolylineData.getDataZ(uid);
144         int size = datax.length;
145
146         if (PolylineData.isXShiftSet(uid) != 0) {
147             double[] x_shift = (double[])PolylineData.getShiftX(uid);
148             for (int i = 0; i < size; i++) {
149                 datax[i] += x_shift[i];
150             }
151         }
152
153         if (PolylineData.isYShiftSet(uid) != 0) {
154             double[] y_shift = (double[])PolylineData.getShiftY(uid);
155             for (int i = 0; i < size; i++) {
156                 datay[i] += y_shift[i];
157             }
158         }
159
160         if (PolylineData.isZShiftSet(uid) != 0) {
161             double[] z_shift = (double[])PolylineData.getShiftZ(uid);
162             for (int i = 0; i < size; i++) {
163                 dataz[i] += z_shift[i];
164             }
165         }
166
167         return CallRenderer.getPixelFrom3dCoordinates(axes, datax, datay, dataz);
168     }
169
170     public static double[] getNearestSegmentIndex(double[][] data, double x, double y) {
171         if (data != null && data.length >= 1) {
172             int minIndex = 0;
173             double minD2 = Double.MAX_VALUE;
174
175             for (int i = 0; i < data.length - 1; i++) {
176                 final double[] p1 = data[i];
177                 final double[] p2 = data[i + 1];
178                 final double px = p2[0] - p1[0];
179                 final double py = p2[1] - p1[1];
180                 final double mpx = x - p1[0];
181                 final double mpy = y - p1[1];
182                 final double ps = mpx * px + mpy * py;
183                 final double d2;
184
185                 if (0 <= ps) {
186                     final double n2 = px * px + py * py;
187                     // the projected point is in the segment [p1,p2]
188                     if (ps <= n2) {
189                         d2 = mpx * mpx + mpy * mpy - ps * ps / n2;
190                     } else {
191                         d2 = (x - p2[0]) * (x - p2[0]) + (y - p2[1]) * (y - p2[1]);
192                     }
193                 } else {
194                     d2 = mpx * mpx + mpy * mpy;
195                 }
196
197                 if (d2 < minD2) {
198                     minD2 = d2;
199                     minIndex = i;
200                 }
201             }
202
203             final double[] p1 = data[minIndex];
204             final double[] p2 = data[minIndex + 1];
205             final double px = p2[0] - p1[0];
206             final double py = p2[1] - p1[1];
207             final double ps = (x - p1[0]) * px + (y - p1[1]) * py;
208             double[] info = new double[] {minIndex, 0};
209
210             if (0 <= ps) {
211                 final double n2 = px * px + py * py;
212                 // the projected point is in the segment [p1,p2]
213                 if (ps <= n2) {
214                     info[1] = ps / n2;
215                 } else {
216                     info[1] = 1;
217                 }
218             }
219
220             return info;
221         }
222
223         return null;
224     }
225
226     public static double[] getNearestSegmentIndex(double[][] data, double x, double y, double z) {
227         if (data != null && data.length >= 1) {
228             int minIndex = 0;
229             double minD2 = Double.MAX_VALUE;
230
231             for (int i = 0; i < data.length - 1; i++) {
232                 final double[] p1 = data[i];
233                 final double[] p2 = data[i + 1];
234                 final double px = p2[0] - p1[0];
235                 final double py = p2[1] - p1[1];
236                 final double pz = p2[2] - p1[2];
237                 final double mpx = x - p1[0];
238                 final double mpy = y - p1[1];
239                 final double mpz = z - p1[2];
240                 final double ps = mpx * px + mpy * py + mpz * pz;
241                 final double d2;
242
243                 if (0 <= ps) {
244                     final double n2 = px * px + py * py + pz * pz;
245                     // the projected point is in the segment [p1,p2]
246                     if (ps <= n2) {
247                         d2 = mpx * mpx + mpy * mpy + mpz * mpz - ps * ps / n2;
248                     } else {
249                         d2 = (x - p2[0]) * (x - p2[0]) + (y - p2[1]) * (y - p2[1]) + (z - p2[2]) * (z - p2[2]);
250                     }
251                 } else {
252                     d2 = mpx * mpx + mpy * mpy + mpz * mpz;
253                 }
254
255                 if (d2 < minD2) {
256                     minD2 = d2;
257                     minIndex = i;
258                 }
259             }
260
261             final double[] p1 = data[minIndex];
262             final double[] p2 = data[minIndex + 1];
263             final double px = p2[0] - p1[0];
264             final double py = p2[1] - p1[1];
265             final double pz = p2[2] - p1[2];
266             final double ps = (x - p1[0]) * px + (y - p1[1]) * py + (z - p1[2]) * pz;
267             double[] info = new double[] {minIndex, 0};
268
269             if (0 <= ps) {
270                 final double n2 = px * px + py * py + pz * pz;
271                 // the projected point is in the segment [p1,p2]
272                 if (ps <= n2) {
273                     info[1] = ps / n2;
274                 } else {
275                     info[1] = 1;
276                 }
277             }
278
279             return info;
280         }
281
282         return null;
283     }
284
285     public static double[] getNearestSegmentIndex(Integer axes, Integer uid, int x, int y) {
286         return getNearestSegmentIndex(EntityPicker.getPixelsData(axes, uid), (double) x, (double) y);
287     }
288
289     public static double[] getNearestSegmentIndex(Integer uid, double x, double y, double z) {
290         return getNearestSegmentIndex(DatatipCommon.getPolylineDataMatrix(uid, false), x, y, z);
291     }
292
293     public static double[] getNearestSegmentIndex(Integer uid, double x, double y) {
294         return getNearestSegmentIndex(DatatipCommon.getPolylineDataMatrix(uid, true), x, y);
295     }
296
297     /**
298      * Check algorithm linear interpolation for each pair of points.
299      * @param uid    Polyline uid to be checked.
300      * @param x        position on x axis in view coordinates.
301      * @param y        position on y axis in view coordinates.
302      * @return        true if x,y belongs or is closest to the polyline.
303      */
304     private int isOverLine(Integer axes, Integer uid, int x, int y) {
305         double[][] pixs = EntityPicker.getPixelsData(axes, uid);
306         for (int i = 0; i < pixs.length - 1; i++) {
307             // We could probably make something more efficient
308             // but in the case of datatip it is probably useless...
309             // So wait and see the user feedback.
310             final double[] p1 = pixs[i];
311             final double[] p2 = pixs[i + 1];
312             final double px = p2[0] - p1[0];
313             final double py = p2[1] - p1[1];
314             final double mpx = x - p1[0];
315             final double mpy = y - p1[1];
316             final double ps = mpx * px + mpy * py;
317
318             // the projected point is in the segment [p1,p2]
319             if (0 <= ps) {
320                 final double n2 = px * px + py * py;
321                 if (ps <= n2) {
322                     final double d = mpx * mpx + mpy * mpy - ps * ps / n2;
323                     // the square of the distance is lower than EPS^2
324                     if (d <= EPS * EPS) {
325                         return i;
326                     }
327                 }
328             }
329         }
330
331         return -1;
332     }
333
334     /**
335      * Checks if the given point belongs the polyline dot.
336      * @param uid    Polyline uid to be checked.
337      * @param x        position on x axis in view coordinates.
338      * @param y        position on y axis in view coordinates.
339      * @return        True if x,y belongs to the polyline mark.
340      */
341     private int isOverDot(Integer axes, Integer uid, int x, int y) {
342         double[][] pixs = EntityPicker.getPixelsData(axes, uid);
343         for (int i = 0; i < pixs.length; ++i) {
344             if ((Math.abs(x - pixs[i][0]) <= EPS + 1) && (Math.abs(y - pixs[i][1]) <= EPS + 1)) {
345                 return i;
346             }
347         }
348
349         return -1;
350     }
351
352     /**
353      * Checks if the given point belongs the polyline dot.
354      * @param uid    Polyline uid to be checked.
355      * @param x        position on x axis in view coordinates.
356      * @param y        position on y axis in view coordinates.
357      * @return        True if x,y belongs to the polyline mark.
358      */
359     private int isOverBar(Integer axes, Integer uid, int x, int y, int width) {
360         double[][] pixs = EntityPicker.getPixelsData(axes, uid);
361         for (int i = 0; i < pixs.length; ++i) {
362             if ((Math.abs(x - pixs[i][0]) <= width) && (Math.abs(y - pixs[i][1]) <= EPS)) {
363                 return i;
364             }
365         }
366
367         return -1;
368     }
369
370     /**
371      * Checks if the given point belongs the polyline mark.
372      * @param uid    Polyline uid to be checked.
373      * @param x        position on x axis in view coordinates.
374      * @param y        position on y axis in view coordinates.
375      * @return        True if x,y belongs to the polyline mark.
376      */
377     private int isOverMark(Integer axes, Integer uid, int x, int y) {
378         double[][] pixs = EntityPicker.getPixelsData(axes, uid);
379         Integer size = CommonHandler.getMarkSize(uid);
380         Integer unit = CommonHandler.getMarkSizeUnit(uid);
381
382         int finalSize = (unit == 1) ? (8 + 2 * size) : size;
383         finalSize /= 2;
384
385         for (int i = 0; i < pixs.length; ++i) {
386             if ((Math.abs(x - pixs[i][0]) <= finalSize) && (Math.abs(y - pixs[i][1]) <= finalSize)) {
387                 return i;
388             }
389         }
390
391         return -1;
392     }
393
394     public class PickedPoint {
395         public int point;
396         public boolean isSegment;
397         PickedPoint(int p, boolean segment) {
398             point = p;
399             isSegment = segment;
400         }
401
402         public String toString() {
403             return "Point: " + point + ", isSegment=" + isSegment;
404         }
405     }
406
407     /**
408      * Given a polyline uid checks if the given point (px,py)
409      * belongs or is closer to any polyline point.
410      *
411      * @return The picked point or PickedPoint.point = -1 otherwise.
412      */
413     public PickedPoint pickPoint(Integer uid, int px, int py) {
414         PickedPoint point = new PickedPoint(-1, false);
415         Integer[] position = new Integer[] {px, py};
416         Integer figUid = (new ObjectSearcher()).searchParent(uid, GraphicObjectProperties.__GO_FIGURE__);
417         Integer axes = AxesHandler.clickedAxes(figUid, position);
418         if (axes == null) {
419             return point;
420         }
421
422         if (CommonHandler.isMarkEnabled(uid)) {
423             point.point = isOverMark(axes, uid, px, py);
424             if (point.point != -1) {
425                 return point;
426             }
427         }
428
429         if (CommonHandler.isLineEnabled(uid)) {
430             point.point = isOverLine(axes, uid, px, py);
431             point.isSegment = true;
432         } else {
433             point.point = isOverDot(axes, uid, px, py);
434         }
435
436         return point;
437     }
438
439     /**
440      * Checks if the axes is in default view (2d view).
441      *
442      * @return true if is in default view, false otherwise.
443      */
444     private boolean isInDefaultView(Axes axes) {
445         Double rot[] = axes.getRotationAngles();
446         return (rot[0] == 0.0 && rot[1] == 270.0);
447     }
448
449     /**
450      * Project the given point in view plane.
451      *
452      * @return the projected point.
453      */
454     private double[] transformPoint(Axes axes, double x, double y, double z) {
455         double point[] = {x, y, z};
456         return AxesDrawer.compute2dViewCoordinates(axes, point);
457     }
458
459
460     public class LegendInfo {
461         public Integer legend = null;
462         public Integer polyline = null;
463         LegendInfo(Integer legend, Integer polyline) {
464             this.legend = legend;
465             this.polyline = polyline;
466         }
467     }
468
469     /**
470      * Check if the given position is over a legend object.
471      *
472      * @param axes The uid of axes that was clicked.
473      * @param position The mouse position (x, y).
474      * @return The LegendInfo if picked a legend null otherwise.
475      */
476     public LegendInfo pickLegend(Integer figure, Integer[] position) {
477         Integer axes = AxesHandler.clickedAxes(figure, position);
478         if (axes == null) {
479             return null;
480         }
481         Integer legend = LegendHandler.searchLegend(axes);
482         if (legend == null) {
483             return null;
484         }
485
486         Integer[] links;
487         Integer[] axesSize = {0, 0};
488         Double delta;
489         Double[] axesBounds = { 0., 0. }, dPosition = { 0., 0. }, legendPos = { 0., 0. }, legendBounds = { 0., 0., 0., 0. }, dimension = { 0., 0. };
490
491         AxesContainer container = (AxesContainer) GraphicController.getController().getObjectFromId(figure);
492         axesSize = container.getAxesSize();
493
494         axesBounds = (Double[])GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_AXES_BOUNDS__);
495         legendPos = (Double[])GraphicController.getController().getProperty(legend, GraphicObjectProperties.__GO_POSITION__);
496         links = (Integer[])GraphicController.getController().getProperty(legend, GraphicObjectProperties.__GO_LINKS__);
497         dPosition[0] = (position[0] - (axesBounds[0] * axesSize[0])) / (axesBounds[2] * axesSize[0]);
498         dPosition[1] = (position[1] - (axesBounds[1] * axesSize[1])) / (axesBounds[3] * axesSize[1]);
499         dimension = (Double[])GraphicController.getController().getProperty(legend, GraphicObjectProperties.__GO_SIZE__);
500         legendBounds[0] =  legendPos[0];
501         legendBounds[1] = legendPos[1];
502         legendBounds[2] = legendPos[0] + dimension[0];
503         legendBounds[3] = legendPos[1] + dimension[1];
504         delta = dimension[1] / (1.0 * links.length);
505
506         if (dPosition[0] >= legendBounds[0] && dPosition[0] <= legendBounds[2] && dPosition[1] >= legendBounds[1] && dPosition[1] <= legendBounds[3]) {
507             for (Integer i = 0; i < links.length; i++) {
508                 if (dPosition[1] >= (legendBounds[1] + i * delta) && dPosition[1] <= (legendBounds[1] + (i + 1) * delta)) {
509                     return new LegendInfo(legend, links[links.length - 1 - i]);
510                 }
511             }
512         }
513         return null;
514     }
515
516     /**
517      * Try pick an axis label at the given position.
518      * @param figure The figure uid.
519      * @param pos The position (x, y).
520      * @return The picked axis label or null.
521      */
522     public static AxesHandler.axisTo pickLabel(Integer figure, Integer[] pos) {
523
524         Integer axes = AxesHandler.clickedAxes(figure, pos);
525         if (axes == null) {
526             return null;
527         }
528         Double[] corners;
529         Double radAngle = 0.;
530         int rotate = 0;
531         double[] point = new double[3];
532         double[] coord;
533         Integer[] label = {  (Integer)GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_X_AXIS_LABEL__),
534                              (Integer)GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_Y_AXIS_LABEL__),
535                              (Integer)GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_Z_AXIS_LABEL__),
536                              (Integer)GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_TITLE__)
537                           };
538
539         boolean[] logScale = {  (Boolean)GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_X_AXIS_LOG_FLAG__),
540                                 (Boolean)GraphicController.getController().getProperty(axes, GraphicObjectProperties.__GO_Y_AXIS_LOG_FLAG__)
541                              };
542
543         for (Integer i = 0; i < 4; i++) {
544             corners = (Double[])GraphicController.getController().getProperty(label[i], GraphicObjectProperties.__GO_CORNERS__);
545             radAngle = (Double)GraphicController.getController().getProperty(label[i], GraphicObjectProperties.__GO_FONT_ANGLE__);
546             rotate = ((int)((radAngle * 2) / Math.PI)) % 2;
547             if (rotate == 1) {
548                 corners = rotateCorners(corners);
549             }
550             point[0] = pos[0];
551             point[1] = pos[1];
552             point[2] = 1.0;
553             coord = CallRenderer.get2dViewFromPixelCoordinates(axes, point);
554
555             coord[0] = CommonHandler.InverseLogScale(coord[0], logScale[0]);
556             coord[1] = CommonHandler.InverseLogScale(coord[1], logScale[1]);
557
558             if ((coord[0] >= corners[0] && coord[0] <= corners[6]) || (coord[0] <= corners[0] && coord[0] >= corners[6])) {
559                 if ((coord[1] >= corners[1] && coord[1] <= corners[4]) || (coord[1] <= corners[1] && coord[1] >= corners[4])) {
560                     switch (i) {
561                         case 0:
562                             return AxesHandler.axisTo.__X__;
563                         case 1:
564                             return AxesHandler.axisTo.__Y__;
565                         case 2:
566                             return AxesHandler.axisTo.__Z__;
567                         case 3:
568                             return AxesHandler.axisTo.__TITLE__;
569                     }
570                 }
571             }
572         }
573         return null;
574     }
575
576     /**
577      * Rotate the Corners points 90 degrees
578      *
579      * @param vec The corners points vector(should be 4 points x 3 coord, arranged sequentially)
580      */
581     private static Double[] rotateCorners(Double[] vec) {
582
583         Integer length = vec.length;
584         Double[] newVec = new Double[length];
585         for (Integer i = 0; i < length - 3; i++) {
586             newVec[i + 3] = vec[i];
587         }
588         newVec[0] = vec[length - 3];
589         newVec[1] = vec[length - 2];
590         newVec[2] = vec[length - 1];
591         return newVec;
592     }
593
594
595     public class SurfaceInfo {
596         public Integer surface = null;
597         public Vector3d point = null;
598     }
599
600     /*
601      * Given a figure search for all plot3d's and for each surface
602      * test if the ray given by the mouse position intersects any surface
603      * @param figure Figure unique identifier.
604      * @param mousePos Mouse position (x, y).
605      * @return The SurfaceInfo if picked a surface null otherwise.
606      */
607     public SurfaceInfo pickSurface(Integer figure, Integer[] mousePos) {
608         Integer uid = AxesHandler.clickedAxes(figure, mousePos);
609         Axes curAxes = (Axes)GraphicController.getController().getObjectFromId(uid);
610         if (curAxes == null) {
611             return null;
612         }
613
614         double[][] factors = curAxes.getScaleTranslateFactors();
615         //double[] mat = DrawerVisitor.getVisitor(figure).getAxesDrawer().getProjection(uid).getMatrix();
616
617         Vector3d v0 = AxesDrawer.unProject(curAxes, new Vector3d(1.0f * mousePos[0], 1.0f * mousePos[1], 0.0));
618         Vector3d v1 = AxesDrawer.unProject(curAxes, new Vector3d(1.0f * mousePos[0], 1.0f * mousePos[1], 1.0));
619         v0 = new Vector3d((v0.getX() - factors[1][0]) / factors[0][0], (v0.getY() - factors[1][1]) / factors[0][1], (v0.getZ() - factors[1][2]) / factors[0][2]);
620         v1 = new Vector3d((v1.getX() - factors[1][0]) / factors[0][0], (v1.getY() - factors[1][1]) / factors[0][1], (v1.getZ() - factors[1][2]) / factors[0][2]);
621
622         Vector3d Dir = v0.minus(v1).getNormalized();
623         // ray is the parametric cuve t -> v0 + T * Dir
624         Integer[] types = {GraphicObjectProperties.__GO_PLOT3D__, GraphicObjectProperties.__GO_FAC3D__, GraphicObjectProperties.__GO_GRAYPLOT__};
625         Integer[] objs = (new ObjectSearcher()).searchMultiple(figure, types);
626         double T = Double.NEGATIVE_INFINITY;
627         SurfaceInfo info = new SurfaceInfo();
628         if (objs != null) {
629             for (int i = 0; i < objs.length; ++i) {
630                 double curT = SurfaceData.pickSurface(objs[i], v0.getX(), v0.getY(), v0.getZ(), Dir.getX(), Dir.getY(), Dir.getZ());
631                 if (curT > T) {
632                     T = curT;
633                     info.surface = objs[i];
634                 }
635             }
636             if (info.surface != null) {
637                 info.point = new Vector3d(v0.plus(Dir.times(T))); // point = v0 + T * Dir
638             }
639             // if formerly used normalized Z \in [-1,1] is to be computed:
640             // Z = info.point.getX() * mat[2] + info.point.getY() * mat[6] + info.point.getZ() * mat[10] + mat[14];
641         }
642         return info;
643     }
644
645
646     public Integer pickDatatip(Integer figure, Integer[] pos) {
647         Integer axes = AxesHandler.clickedAxes(figure, pos);
648         if (axes == null) {
649             return null;
650         }
651
652         Integer[] datatips = (new ObjectSearcher()).search(axes, GraphicObjectProperties.__GO_DATATIP__, true);
653         if (datatips != null) {
654             int x = pos[0];
655             int y = pos[1];
656
657             for (int i = 0; i < datatips.length; ++i) {
658                 Double[] tip_pos = (Double[])GraphicController.getController().getProperty(datatips[i], GraphicObjectProperties.__GO_DATATIP_DATA__);
659                 double[] point = new double[] {tip_pos[0], tip_pos[1], tip_pos[2]};
660                 double[] pix = CallRenderer.getPixelFrom3dCoordinates(axes, point);
661
662                 Integer size = CommonHandler.getMarkSize(datatips[i]);
663                 Integer unit = CommonHandler.getMarkSizeUnit(datatips[i]);
664
665                 int finalSize = (unit == 1) ? (8 + 2 * size) : size;
666                 finalSize /= 2;
667
668                 if ((Math.abs(x - pix[0]) <= finalSize) && (Math.abs(y - pix[1]) <= finalSize)) {
669                     return datatips[i];
670                 }
671             }
672         }
673
674         return null;
675     }
676 }
677