Add interactive zoom_rect.
[scilab.git] / scilab / modules / renderer / src / cpp / subwinDrawing / Camera.cpp
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2007 - INRIA - Jean-Baptiste Silvy
4  * desc : File used to position viewpoint and rendering zone in the
5  * canvas
6  * 
7  * This file must be used under the terms of the CeCILL.
8  * This source file is licensed as described in the file COPYING, which
9  * you should have received as part of this distribution.  The terms
10  * are also available at    
11  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
12  *
13  */
14
15 #include "Camera.h"
16 #include "DrawableSubwin.h"
17
18 extern "C"
19 {
20 #include "math_graphics.h"
21 #include "GetProperty.h"
22 #include "SetProperty.h"
23 }
24
25 namespace sciGraphics
26 {
27
28 /*--------------------------------------------------------------------------*/
29 Camera::Camera( sciPointObj * pObj ) : DrawableObject(pObj)
30 {
31   
32 }
33 /*--------------------------------------------------------------------------*/
34 Camera::~Camera( void )
35 {
36   
37 }
38 /*--------------------------------------------------------------------------*/
39 void Camera::setViewingArea( double axesBounds[4], double margins[4] )
40 {
41   double translation[2] ;
42   // T = Taxes + SizeAxes * [marginLeft, marginTop].
43   translation[0] = axesBounds[0] + axesBounds[2] * margins[0] ;
44   translation[1] = 1.0 - axesBounds[1] - axesBounds[3] * (1.0 - margins[3]) ;
45
46   // S = Saxes - margin.Saxes
47   // where Saxes = [w,h] and margin = [margin_left+margin_right, margin_top+margin_bottom].
48   double scale[2] ;
49   scale[0] = (1.0 - margins[0] - margins[1]) * axesBounds[2] ;
50   scale[1] = (1.0 - margins[2] - margins[3]) * axesBounds[3] ;
51   getCameraImp()->setViewingArea(translation, scale);
52 }
53 /*--------------------------------------------------------------------------*/
54 void Camera::setRotationAngles( double alpha, double theta )
55 {
56   getCameraImp()->setAxesRotation(alpha, theta);
57 }
58 /*--------------------------------------------------------------------------*/
59 void Camera::setAxesReverse(BOOL axesReverse[3])
60 {
61   getCameraImp()->setAxesReverse(axesReverse[0] == TRUE,
62                                  axesReverse[1] == TRUE,
63                                  axesReverse[2] == TRUE);
64 }
65 /*--------------------------------------------------------------------------*/
66 void Camera::setSubwinBox( double bounds[6] )
67 {
68
69   double boxCenter[3];
70   boxCenter[0] = (bounds[0] + bounds[1]) / 2.0 ;
71   boxCenter[1] = (bounds[2] + bounds[3]) / 2.0 ;
72   boxCenter[2] = (bounds[4] + bounds[5]) / 2.0 ;
73   getCameraImp()->setAxesCenter(boxCenter) ;
74   
75   double scale[3] ;
76   // 1.0 / ( Xmax - Xmin )
77   scale[0] = 1.0 / (bounds[1] - bounds[0]) ;
78   scale[1] = 1.0 / (bounds[3] - bounds[2]) ;
79   scale[2] = 1.0 / (bounds[5] - bounds[4]) ;
80   
81   getCameraImp()->setAxesNormalizationScale(scale) ;
82
83   if (sciGetIsCubeScaled(m_pDrawed))
84   {
85     getCameraImp()->setAxesFittingScale(scale);
86   }
87   else
88   {
89     // preserve isometry by applying same scale
90     double minScale = Min(scale[0], Min(scale[1], scale[2]));
91     double fittingScale[3] = {minScale, minScale, minScale};
92     getCameraImp()->setAxesFittingScale(fittingScale);
93   }
94   
95
96   double trans[3];
97   // put the min bounds to our origin
98   trans[0] = -bounds[0] ;
99   trans[1] = -bounds[2] ;
100   trans[2] = bounds[4] ;
101   getCameraImp()->setAxesTranslation(trans) ;
102
103 }
104 /*--------------------------------------------------------------------------*/
105 void Camera::setCameraParameters(void)
106 {
107   // here m_pDrawed is the subwin
108   setViewingArea(sciGetAxesBounds(m_pDrawed), sciGetMargins(m_pDrawed)) ;
109
110   double bounds[6] ;
111   sciGetRealDataBounds(m_pDrawed, bounds) ;
112   setSubwinBox(bounds) ;
113
114   double alpha;
115   double theta;
116   sciGetViewingAngles(m_pDrawed, &alpha, &theta);
117   setRotationAngles(alpha, theta);
118
119   BOOL axesReverse[3];
120   sciGetAxesReverse(m_pDrawed, axesReverse);
121   setAxesReverse(axesReverse);
122 }
123 /*--------------------------------------------------------------------------*/
124 void Camera::renderPosition( void )
125 {
126   getCameraImp()->renderPosition();
127 }
128 /*--------------------------------------------------------------------------*/
129 void Camera::replaceCamera( void )
130 {
131   getCameraImp()->replaceCamera();
132 }
133 /*--------------------------------------------------------------------------*/
134 void Camera::getPixelCoordinates(const double userCoord[3], int pixCoord[2])
135 {
136   getCameraImp()->getPixelCoordinates(userCoord, pixCoord);
137 }
138 /*--------------------------------------------------------------------------*/
139 void Camera::getPixelCoordinates(const double userCoords[3], double pixCoords[3])
140 {
141   getCameraImp()->getPixelCoordinates(userCoords, pixCoords);
142 }
143 /*--------------------------------------------------------------------------*/
144 void Camera::get2dViewPixelCoordinates(const double userCoord[3], int pixCoord[2])
145 {
146   getCameraImp()->get2dViewPixelCoordinates(userCoord, pixCoord);
147 }
148 /*--------------------------------------------------------------------------*/
149 void Camera::getSceneCoordinates(const double pixCoords[3], double userCoords[3])
150 {
151   getCameraImp()->getSceneCoordinates(pixCoords, userCoords);
152 }
153 /*--------------------------------------------------------------------------*/
154 void Camera::get2dViewCoordinates(const int pixCoords[2], double userCoord2D[2])
155 {
156   getCameraImp()->get2dViewCoordinates(pixCoords, userCoord2D);
157 }
158 /*--------------------------------------------------------------------------*/
159 void Camera::get2dViewCoordinates(const double userCoords[3], double coord2D[2])
160 {
161   int pixCoords[2];
162   // convert usetr coords to pixel ones
163   getPixelCoordinates(userCoords, pixCoords);
164
165   // convert pixel ones to 2d frame ones
166   get2dViewCoordinates(pixCoords, coord2D);
167 }
168 /*--------------------------------------------------------------------------*/
169 void Camera::draw( void )
170 {
171   initializeDrawing();
172   setCameraParameters();
173   getCameraImp()->renderPosition();
174   endDrawing();
175 }
176 /*--------------------------------------------------------------------------*/
177 void Camera::show( void )
178 {
179   initializeDrawing();
180   getCameraImp()->show();
181   endDrawing();
182 }
183 /*------------------------------------------------------------------------------------------*/
184 bool Camera::zoomRect(int posX, int posY, int width, int height)
185 {
186   double rectCorners[4][2] = {{posX        , posY + height},
187                               {posX        , posY         },
188                               {posX + width, posY         },
189                               {posX + width, posY + height}};
190
191   return zoomRect(rectCorners);
192
193 }
194 /*------------------------------------------------------------------------------------------*/
195 bool Camera::zoomRect(const double corners[4][2])
196 {
197
198   // the aim of thi salgorithm is to perform zomming inside a 3D axes
199   // knowing a rectangle in pixels
200   // In 3d the selection rectangle can be extended as a infinite
201   // tube with a rectangular section aligned with view
202   // Ideally, the viewing area after the zoom should be the intersection
203   // of the selection volume and the axes box.
204   // However, in Scilab the axes box always remains a box.
205   // Consequently to perform a zoom, we compute the extreme bounds of the intersection
206   // in the 3 directions and use them as new bounds for the axes box.
207
208   double oldDataBounds[6];
209
210   // get data bounds, already scaled
211   sciGetRealDataBounds(m_pDrawed, oldDataBounds);
212  
213   // first step compute the 4 lines composing the selection area
214   // in 3d coordinates
215   // the selection area is an infinite cone (tube)
216   // of rectangular section.
217   double selectionLines[4][2][3]; // contains 4 lines which are actually 2 points
218   computeZoomAreaLines(corners, selectionLines);
219
220   
221
222   // second step project lines on each of the 6 axes cube plane
223   // actually we just need the found the bounds of the intersection of
224   // the selectionArea and the axes cube. And the bounds are found on the cube.
225   // So we just need to focus on the 6 faces of the cube
226
227   // set initial bounds
228   // set the otherbound since we wants to found minima
229   // and maxima
230   double oldXmin = oldDataBounds[0];
231   double oldXmax = oldDataBounds[1];
232   double oldYmin = oldDataBounds[2];
233   double oldYmax = oldDataBounds[3];
234   double oldZmin = oldDataBounds[4];
235   double oldZmax = oldDataBounds[5];
236
237   double newXmin = oldDataBounds[1]; // oldXmax
238   double newXmax = oldDataBounds[0]; // oldXmin
239   double newYmin = oldDataBounds[3]; // oldYmax
240   double newYmax = oldDataBounds[2]; // oldYmin
241   double newZmin = oldDataBounds[5]; // oldZMax
242   double newZmax = oldDataBounds[4]; // oldZmin
243
244   // the four intersections with one plane
245   double intersections[4][3];
246
247   // intersection with x = Xmin axis
248   if (   getXaxisIntersections(selectionLines, oldXmin, intersections)
249       && checkXIntersections(intersections, oldYmin, oldYmax, oldZmin, oldZmax))
250   {
251     // ok we found points and the selection intersect the side of the cube
252     // update Y and Z coordinates
253     // don't try to update X here, it's just xMin
254     updateYCoordinate(intersections, oldYmin, oldYmax, newYmin, newYmax);
255     updateZCoordinate(intersections, oldZmin, oldZmax, newZmin, newZmax);
256
257     newXmin = oldXmin;
258   }
259
260  
261
262   // same with x = Xmax axis
263   if (   getXaxisIntersections(selectionLines, oldXmax, intersections)
264       && checkXIntersections(intersections, oldYmin, oldYmax, oldZmin, oldZmax))
265   {
266     updateYCoordinate(intersections, oldYmin, oldYmax, newYmin, newYmax);
267     updateZCoordinate(intersections, oldZmin, oldZmax, newZmin, newZmax);
268
269     newXmax = newXmax;
270   }
271
272   // same with y = Ymin axis
273   if (   getYaxisIntersections(selectionLines, oldYmin, intersections)
274       && checkYIntersections(intersections, oldXmin, oldXmax, oldZmin, oldZmax))
275   {
276     updateXCoordinate(intersections, oldXmin, oldXmax, newXmin, newXmax);
277     updateZCoordinate(intersections, oldZmin, oldZmax, newZmin, newZmax);
278
279     newYmin = oldYmin;
280   }
281
282   // same with y = Ymax axis
283   if (   getYaxisIntersections(selectionLines, oldYmax, intersections)
284       && checkYIntersections(intersections, oldXmin, oldXmax, oldZmin, oldZmax))
285   {
286     updateXCoordinate(intersections, oldXmin, oldXmax, newXmin, newXmax);
287     updateZCoordinate(intersections, oldZmin, oldZmax, newZmin, newZmax);
288
289     newYmax = oldYmax;
290   }
291
292   // same with z = Zmin axis
293   if (   getZaxisIntersections(selectionLines, oldZmin, intersections)
294       && checkZIntersections(intersections, oldXmin, oldXmax, oldYmin, oldYmax))
295   {
296     updateXCoordinate(intersections, oldXmin, oldXmax, newXmin, newXmax);
297     updateYCoordinate(intersections, oldYmin, oldYmax, newYmin, newYmax);
298
299     newZmin = oldZmin;
300   }
301
302   // same with z = Zmax axis
303   if (   getZaxisIntersections(selectionLines, oldZmax, intersections)
304       && checkZIntersections(intersections, oldXmin, oldXmax, oldYmin, oldYmax))
305   {
306     updateXCoordinate(intersections, oldXmin, oldXmax, newXmin, newXmax);
307     updateYCoordinate(intersections, oldYmin, oldYmax, newYmin, newYmax);
308
309     newZmax = oldZmax;
310   }
311
312   // check that the view was not outside
313   // that would mean that the newBounds were not updated
314   if (newXmin >= newXmax && newYmin >= newYmax && newZmin >= newZmax)
315   {
316     // selection was ousite all this work for nothing
317     return false;
318   }
319
320   // some of the bounds have been updated, find which ones.
321   if (newXmin >= newXmax)
322   {
323     // no update here
324     newXmin = oldXmin;
325     newXmax = oldXmax;
326   }
327
328   if (newYmin >= newYmax)
329   {
330     // no update here
331     newYmin = oldYmin;
332     newYmax = oldYmax;
333   }
334
335   if (newZmin >= newZmax)
336   {
337     // no update here
338     newZmin = oldZmin;
339     newZmax = oldZmax;
340   }
341
342
343   // ooray we found new bounds
344   // switch back to Scilab coordinates
345   inversePointScale(newXmin, newYmin, newZmin, &newXmin, &newYmin, &newZmin);
346   inversePointScale(newXmax, newYmax, newZmax, &newXmax, &newYmax, &newZmax);
347   double newDataBounds[6] = {newXmin, newXmax, newYmin, newYmax, newZmin, newZmax};
348
349   sciSetZoomBox(m_pDrawed, newDataBounds);
350
351   return true;
352
353 }
354 /*--------------------------------------------------------------------------*/
355 void Camera::getViewingArea(int * xPos, int * yPos, int * width, int * height)
356 {
357   getCameraImp()->getViewingArea(xPos, yPos, width, height);
358 }
359 /*--------------------------------------------------------------------------*/
360 void Camera::computeZoomAreaLines(const double areaPixCorners[4][2], double areaLines[4][2][3])
361 {
362   for (int i = 0; i < 4; i++)
363   {
364     // get two points along the axis line in pixel coordinates
365     // in pixel coordinates lines are along Z coordinates
366     // so we can specify everything for as Z
367     // let say 0 and 1
368     double point1[3] = {areaPixCorners[i][0], areaPixCorners[i][1], 0.0};
369     double point2[3] = {areaPixCorners[i][0], areaPixCorners[i][1], -1.0};
370
371     // retrieve scene coordinate
372     getSceneCoordinates(point1, areaLines[i][0]);
373     getSceneCoordinates(point2, areaLines[i][1]);
374     pointScale(areaLines[i][0][0], areaLines[i][0][1], areaLines[i][0][2],
375                &areaLines[i][0][0], &areaLines[i][0][1], &areaLines[i][0][2]);
376     pointScale(areaLines[i][1][0], areaLines[i][1][1], areaLines[i][1][2],
377                &areaLines[i][1][0], &areaLines[i][1][1], &areaLines[i][1][2]);
378   }
379 }
380 /*--------------------------------------------------------------------------*/
381 bool Camera::getXaxisIntersections(const double areaLines[4][2][3], double planeXCoord, double intersections[4][3])
382 {
383   for (int i = 0; i < 4; i++)
384   {
385     // for any plane the result of the intersection is is I = (P1 + a.P2) / (a - 1)
386     // where P1 and P2 are 2 points on the line
387     // and a = ||P1P1'|| / ||P2P2'|| where P1' and P2' are the orthogonal projections
388     // of P1 and P2 on the plane
389
390     // It's not needed to care about ||P2P2'|| being 0 with the value we choose for it
391     const double * p1 = areaLines[i][0];
392     const double * p2 = areaLines[i][1];
393     double alpha = (p1[0] - planeXCoord) / (p2[0] - planeXCoord);
394
395     if (alpha == 1.0)
396     {
397       return false;
398     }
399     
400     getIntersection(p1, p2, alpha, intersections[i]);
401   }
402   return true;
403 }
404 /*--------------------------------------------------------------------------*/
405 bool Camera::getYaxisIntersections(const double areaLines[4][2][3], double planeYCoord, double intersections[4][3])
406 {
407   for (int i = 0; i < 4; i++)
408   {
409     // for any plane the result of the intersection is is I = (P1 + a.P2) / (a - 1)
410     // where P1 and P2 are 2 points on the line
411     // and a = ||P1P1'|| / ||P2P2'|| where P1' and P2' are the orthogonal projections
412     // of P1 and P2 on the plane
413
414     // It's not needed to care about ||P2P2'|| being 0 with the value we choose for it
415     const double * p1 = areaLines[i][0];
416     const double * p2 = areaLines[i][1];
417     double alpha = (p1[1] - planeYCoord) / (p2[1] - planeYCoord);
418
419     if (alpha == 1.0)
420     {
421       return false;
422     }
423     
424     getIntersection(p1, p2, alpha, intersections[i]);
425   }
426   return true;
427 }
428 /*--------------------------------------------------------------------------*/
429 bool Camera::getZaxisIntersections(const double areaLines[4][2][3], double planeZCoord, double intersections[4][3])
430 {
431   for (int i = 0; i < 4; i++)
432   {
433     // for any plane the result of the intersection is is I = (a.P2 - P1) / (a - 1)
434     // where P1 and P2 are 2 points on the line
435     // and a = ||P1P1'|| / ||P2P2'|| where P1' and P2' are the orthogonal projections
436     // of P1 and P2 on the plane
437
438     // It's not needed to care about ||P2P2'|| being 0 with the value we choose for it
439     const double * p1 = areaLines[i][0];
440     const double * p2 = areaLines[i][1];
441     double alpha = (p1[2] - planeZCoord) / (p2[2] - planeZCoord);
442
443     if (alpha == 1.0)
444     {
445       return false;
446     }
447     
448     getIntersection(p1, p2, alpha, intersections[i]);
449   }
450   return true;
451 }
452 /*--------------------------------------------------------------------------*/
453 void Camera::getIntersection(const double p1[3], const double p2[3], double alpha, double intersection[3])
454 {
455   scalarMult3D(p2, alpha, intersection); // I = a.P2
456   vectSubstract3D(intersection, p1, intersection); // I = a.P2 - P1
457   scalarMult3D(intersection, 1.0 / (alpha - 1.0), intersection); // I = (P1 + a.P2) / (a - 1)
458 }
459 /*--------------------------------------------------------------------------*/
460 void Camera::updateXCoordinate(const double intersections[4][3],
461                                double oldXmin, double oldXmax,
462                                double & newXmin, double & newXmax)
463 {
464   for (int i = 0; i < 4; i++)
465   {
466     if (intersections[i][0] < newXmin)
467     {
468       // it's a zoom don't set bounds outside of old ones
469       newXmin = Max(intersections[i][0], oldXmin);
470     }
471     else if (intersections[i][0] > newXmax)
472     {
473       // it's a zoom don't set bounds outside of old ones
474       newXmax = Min(intersections[i][0], oldXmax);
475     }
476   }
477 }
478 /*--------------------------------------------------------------------------*/
479 void Camera::updateYCoordinate(const double intersections[4][3],
480                                double oldYmin, double oldYmax,
481                                double & newYmin, double & newYmax)
482 {
483   for (int i = 0; i < 4; i++)
484   {
485     if (intersections[i][1] < newYmin)
486     {
487       // it's a zoom don't set bounds outside of old ones
488       newYmin = Max(intersections[i][1], oldYmin);
489     }
490     else if (intersections[i][1] > newYmax)
491     {
492       // it's a zoom don't set bounds outside of old ones
493       newYmax = Min(intersections[i][1], oldYmax);
494     }
495   }
496 }
497 /*--------------------------------------------------------------------------*/
498 void Camera::updateZCoordinate(const double intersections[4][3],
499                                double oldZmin, double oldZmax,
500                                double & newZmin, double & newZmax)
501 {
502   for (int i = 0; i < 4; i++)
503   {
504     if (intersections[i][2] < newZmin)
505     {
506       // it's a zoom don't set bounds outside of old ones
507       newZmin = Max(intersections[i][2], oldZmin);
508     }
509     else if (intersections[i][2] > newZmax)
510     {
511       // it's a zoom don't set bounds outside of old ones
512       newZmax = Min(intersections[i][2], oldZmax);
513     }
514   }
515 }
516 /*--------------------------------------------------------------------------*/
517 bool Camera::checkXIntersections(const double intersections[4][3],
518                                  double oldYmin, double oldYmax,
519                                  double oldZmin, double oldZmax)
520 {
521   for (int i = 0; i < 4; i++)
522   {
523     if (   intersections[i][1] >= oldYmin && intersections[i][1] <= oldYmax
524         && intersections[i][2] >= oldZmin && intersections[i][2] <= oldZmax)
525     {
526       // at least one point in the side
527       return true;
528     }
529   }
530   return false;
531 }
532 /*--------------------------------------------------------------------------*/
533 bool Camera::checkYIntersections(const double intersections[4][3],
534                                  double oldXmin, double oldXmax,
535                                  double oldZmin, double oldZmax)
536 {
537   for (int i = 0; i < 4; i++)
538   {
539     if (   intersections[i][0] >= oldXmin && intersections[i][0] <= oldXmax
540         && intersections[i][2] >= oldZmin && intersections[i][2] <= oldZmax)
541     {
542       // at least one point in the side
543       return true;
544     }
545   }
546   return false;
547 }
548 /*--------------------------------------------------------------------------*/
549 bool Camera::checkZIntersections(const double intersections[4][3],
550                                  double oldXmin, double oldXmax,
551                                  double oldYmin, double oldYmax)
552 {
553   for (int i = 0; i < 4; i++)
554   {
555     if (   intersections[i][0] >= oldXmin && intersections[i][0] <= oldXmax
556         && intersections[i][1] >= oldYmin && intersections[i][1] <= oldYmax)
557     {
558       // at least one point in the side
559       return true;
560     }
561   }
562   return false;
563 }
564 /*--------------------------------------------------------------------------*/
565 CameraBridge * Camera::getCameraImp( void )
566 {
567   return dynamic_cast<CameraBridge *>(m_pImp);
568 }
569 /*--------------------------------------------------------------------------*/
570 }