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
7 * Copyright (C) 2012 - 2016 - Scilab Enterprises
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.
18 package org.scilab.modules.renderer.utils;
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;
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;
29 import org.scilab.modules.graphic_objects.PolylineData;
30 import org.scilab.modules.graphic_objects.SurfaceData;
31 import org.scilab.modules.renderer.JoGLView.DrawerVisitor;
34 * Given a (x, y) window coord checks
35 * if it is closer or belongs to a polyline.
37 * @author Caio Souza <caioc2bolado@gmail.com>
38 * @author Pedro Souza <bygrandao@gmail.com>
44 public class EntityPicker {
46 private static final int EPS = 2;
49 * Picks a polyline at the given position.
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.
56 public Integer pick(Integer figureUid, Integer posX, Integer posY) {
57 Integer[] position = {posX, posY};
58 Integer axes = AxesHandler.clickedAxes(figureUid, position);
63 double[] pos = {posX.doubleValue(), posY.doubleValue(), 1.0};
64 double[] c2d = CallRenderer.get2dViewFromPixelCoordinates(axes, pos);
66 /* Checks if the click is outside canvas drawable area*/
67 if (AxesHandler.isZoomBoxEnabled(axes) && !AxesHandler.isInZoomBoxBounds(axes, c2d[0], c2d[1])) {
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) {
81 if (CommonHandler.isMarkEnabled(polylines[i])) {
82 if (isOverMark(axes, polylines[i], posX, posY) != -1) {
87 if (!CommonHandler.isLineEnabled(polylines[i])) {
88 if (isOverDot(axes, polylines[i], posX, posY) != -1) {
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);
104 if (datax.length == 1) {
105 return new DatatipCommon.Segment(0, datax[0], datax[0], datay[0], datay[0], dataz[0], dataz[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]);
113 return new DatatipCommon.Segment(index, datax[index], datax[index + 1], datay[index], datay[index + 1], datay[index], datay[index + 1]);
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};
128 final double n2 = px * px + py * py;
129 // the projected point is in the segment [p1,p2]
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;
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];
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];
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];
167 return CallRenderer.getPixelFrom3dCoordinates(axes, datax, datay, dataz);
170 public static double[] getNearestSegmentIndex(double[][] data, double x, double y) {
171 if (data != null && data.length >= 1) {
173 double minD2 = Double.MAX_VALUE;
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;
186 final double n2 = px * px + py * py;
187 // the projected point is in the segment [p1,p2]
189 d2 = mpx * mpx + mpy * mpy - ps * ps / n2;
191 d2 = (x - p2[0]) * (x - p2[0]) + (y - p2[1]) * (y - p2[1]);
194 d2 = mpx * mpx + mpy * mpy;
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};
211 final double n2 = px * px + py * py;
212 // the projected point is in the segment [p1,p2]
226 public static double[] getNearestSegmentIndex(double[][] data, double x, double y, double z) {
227 if (data != null && data.length >= 1) {
229 double minD2 = Double.MAX_VALUE;
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;
244 final double n2 = px * px + py * py + pz * pz;
245 // the projected point is in the segment [p1,p2]
247 d2 = mpx * mpx + mpy * mpy + mpz * mpz - ps * ps / n2;
249 d2 = (x - p2[0]) * (x - p2[0]) + (y - p2[1]) * (y - p2[1]) + (z - p2[2]) * (z - p2[2]);
252 d2 = mpx * mpx + mpy * mpy + mpz * mpz;
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};
270 final double n2 = px * px + py * py + pz * pz;
271 // the projected point is in the segment [p1,p2]
285 public static double[] getNearestSegmentIndex(Integer axes, Integer uid, int x, int y) {
286 return getNearestSegmentIndex(EntityPicker.getPixelsData(axes, uid), (double) x, (double) y);
289 public static double[] getNearestSegmentIndex(Integer uid, double x, double y, double z) {
290 return getNearestSegmentIndex(DatatipCommon.getPolylineDataMatrix(uid, false), x, y, z);
293 public static double[] getNearestSegmentIndex(Integer uid, double x, double y) {
294 return getNearestSegmentIndex(DatatipCommon.getPolylineDataMatrix(uid, true), x, y);
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.
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;
318 // the projected point is in the segment [p1,p2]
320 final double n2 = px * px + py * py;
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) {
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.
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)) {
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.
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)) {
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.
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);
382 int finalSize = (unit == 1) ? (8 + 2 * size) : size;
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)) {
394 public class PickedPoint {
396 public boolean isSegment;
397 PickedPoint(int p, boolean segment) {
402 public String toString() {
403 return "Point: " + point + ", isSegment=" + isSegment;
408 * Given a polyline uid checks if the given point (px,py)
409 * belongs or is closer to any polyline point.
411 * @return The picked point or PickedPoint.point = -1 otherwise.
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);
422 if (CommonHandler.isMarkEnabled(uid)) {
423 point.point = isOverMark(axes, uid, px, py);
424 if (point.point != -1) {
429 if (CommonHandler.isLineEnabled(uid)) {
430 point.point = isOverLine(axes, uid, px, py);
431 point.isSegment = true;
433 point.point = isOverDot(axes, uid, px, py);
440 * Checks if the axes is in default view (2d view).
442 * @return true if is in default view, false otherwise.
444 private boolean isInDefaultView(Axes axes) {
445 Double rot[] = axes.getRotationAngles();
446 return (rot[0] == 0.0 && rot[1] == 270.0);
450 * Project the given point in view plane.
452 * @return the projected point.
454 private double[] transformPoint(Axes axes, double x, double y, double z) {
455 double point[] = {x, y, z};
456 return AxesDrawer.compute2dViewCoordinates(axes, point);
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;
470 * Check if the given position is over a legend object.
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.
476 public LegendInfo pickLegend(Integer figure, Integer[] position) {
477 Integer axes = AxesHandler.clickedAxes(figure, position);
481 Integer legend = LegendHandler.searchLegend(axes);
482 if (legend == null) {
487 Integer[] axesSize = {0, 0};
489 Double[] axesBounds = { 0., 0. }, dPosition = { 0., 0. }, legendPos = { 0., 0. }, legendBounds = { 0., 0., 0., 0. }, dimension = { 0., 0. };
491 AxesContainer container = (AxesContainer) GraphicController.getController().getObjectFromId(figure);
492 axesSize = container.getAxesSize();
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);
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]);
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.
522 public static AxesHandler.axisTo pickLabel(Integer figure, Integer[] pos) {
524 Integer axes = AxesHandler.clickedAxes(figure, pos);
529 Double radAngle = 0.;
531 double[] point = new double[3];
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__)
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__)
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;
548 corners = rotateCorners(corners);
553 coord = CallRenderer.get2dViewFromPixelCoordinates(axes, point);
555 coord[0] = CommonHandler.InverseLogScale(coord[0], logScale[0]);
556 coord[1] = CommonHandler.InverseLogScale(coord[1], logScale[1]);
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])) {
562 return AxesHandler.axisTo.__X__;
564 return AxesHandler.axisTo.__Y__;
566 return AxesHandler.axisTo.__Z__;
568 return AxesHandler.axisTo.__TITLE__;
577 * Rotate the Corners points 90 degrees
579 * @param vec The corners points vector(should be 4 points x 3 coord, arranged sequentially)
581 private static Double[] rotateCorners(Double[] vec) {
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];
588 newVec[0] = vec[length - 3];
589 newVec[1] = vec[length - 2];
590 newVec[2] = vec[length - 1];
595 public class SurfaceInfo {
596 public Integer surface = null;
597 public Vector3d point = null;
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.
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) {
614 double[][] factors = curAxes.getScaleTranslateFactors();
615 //double[] mat = DrawerVisitor.getVisitor(figure).getAxesDrawer().getProjection(uid).getMatrix();
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]);
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();
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());
633 info.surface = objs[i];
636 if (info.surface != null) {
637 info.point = new Vector3d(v0.plus(Dir.times(T))); // point = v0 + T * Dir
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];
646 public Integer pickDatatip(Integer figure, Integer[] pos) {
647 Integer axes = AxesHandler.clickedAxes(figure, pos);
652 Integer[] datatips = (new ObjectSearcher()).search(axes, GraphicObjectProperties.__GO_DATATIP__, true);
653 if (datatips != null) {
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);
662 Integer size = CommonHandler.getMarkSize(datatips[i]);
663 Integer unit = CommonHandler.getMarkSizeUnit(datatips[i]);
665 int finalSize = (unit == 1) ? (8 + 2 * size) : size;
668 if ((Math.abs(x - pix[0]) <= finalSize) && (Math.abs(y - pix[1]) <= finalSize)) {