Salome HOME
3ec22e82fe19b833e0b26c7387d882d3c77cab21
[modules/gui.git] / src / GLViewer / GLViewer_Widget.cxx
1 // File:      GLViewer_Widget.cxx
2 // Created:   November, 2004
3 // Author:    OCC team
4 // Copyright (C) CEA 2004
5
6 //================================================================
7 // Class       : GLViewer_Widget
8 // Description : OpenGL QWidget for GLViewer
9 //================================================================
10
11 #include "GLViewer_Widget.h"
12 #include "GLViewer_ViewPort2d.h"
13 #include "GLViewer_Viewer2d.h"
14 #include "GLViewer_Compass.h"
15 #include "GLViewer_Grid.h"
16 #include "GLViewer_Object.h"
17 #include "GLViewer_CoordSystem.h"
18
19 #include <cmath>
20 using namespace std;
21
22 #include <qevent.h>
23 #include <qrect.h>
24
25 #include <qpixmap.h>
26 #include <qimage.h>
27 #include <qapplication.h>
28 #include <qintdict.h>
29 #include <qpaintdevicemetrics.h>
30 #include <qsize.h>
31 #include <qtooltip.h>
32
33 //=======================================================================
34 // Function: GLViewer_Widget
35 // Purpose :
36 //=======================================================================
37 GLViewer_Widget::GLViewer_Widget( QWidget* parent, const char* name ):
38 QGLWidget( parent, 0/*, WRepaintNoErase | WResizeNoErase*/ )
39 {
40   myViewPort = ( GLViewer_ViewPort2d* )parent;
41
42   myXPan = 0.0;
43   myYPan = 0.0;
44   myZPan = 0.0;
45   myXScale = 1.0;
46   myYScale = 1.0;
47   myZScale = 1.0;
48   myRotationAngle = 0.0;
49   myRotationCenterX = 0.0;
50   myRotationCenterY = 0.0;
51   myRotationCenterZ = 1.0;
52   myRotationAnglePrev = 0.0;
53
54   myStart = GL_TRUE;
55
56   isExportMode = false;
57
58   //init();
59   setMouseTracking( true );
60 }
61
62 //=======================================================================
63 // Function: GLViewer_Widget
64 // Purpose :
65 //=======================================================================
66 GLViewer_Widget::~GLViewer_Widget()
67 {
68 }
69
70 //=======================================================================
71 // Function: ~GLViewer_Widget
72 // Purpose :
73 //=======================================================================
74 void GLViewer_Widget::getPan( GLfloat& xPan, GLfloat& yPan, GLfloat& zPan )
75 {
76   xPan = myXPan;
77   yPan = myYPan;
78   zPan = myZPan;
79 }
80
81 //=======================================================================
82 // Function: setPan
83 // Purpose :
84 //=======================================================================
85 void GLViewer_Widget::setPan( GLfloat xPan, GLfloat yPan, GLfloat zPan )
86 {
87   myXPan = xPan;
88   myYPan = yPan;
89   myZPan = zPan;
90 }
91
92 //=======================================================================
93 // Function: getScale
94 // Purpose :
95 //=======================================================================
96 void GLViewer_Widget::getScale( GLfloat& xScale, GLfloat& yScale, GLfloat& zScale )
97 {
98   xScale = myXScale;
99   yScale = myYScale;
100   zScale = myZScale;
101 }
102
103 //=======================================================================
104 // Function: setScale
105 // Purpose :
106 //=======================================================================
107 void GLViewer_Widget::setScale( GLfloat xScale, GLfloat yScale, GLfloat zScale )
108 {
109   if ( xScale > 0 && yScale > 0 && zScale > 0 )
110   {
111     myXScale = xScale;
112     myYScale = yScale;
113     myZScale = zScale;
114   }
115 }
116
117 //=======================================================================
118 // Function: getRotationStart
119 // Purpose :
120 //=======================================================================
121 void GLViewer_Widget::getRotationStart( GLfloat& rotationStartX,
122                                         GLfloat& rotationStartY,
123                                         GLfloat& rotationStartZ )
124 {
125     rotationStartX = myRotationStartX;
126     rotationStartY = myRotationStartY;
127     rotationStartZ = myRotationStartZ;
128 }
129
130 //=======================================================================
131 // Function: setRotationStart
132 // Purpose :
133 //=======================================================================
134 void GLViewer_Widget::setRotationStart( GLfloat rotationStartX,
135                                         GLfloat rotationStartY,
136                                         GLfloat rotationStartZ )
137 {
138     myRotationStartX = rotationStartX;
139     myRotationStartY = rotationStartY;
140     myRotationStartZ = rotationStartZ;
141 }
142
143 //=======================================================================
144 // Function: getRotation
145 // Purpose :
146 //=======================================================================
147 void GLViewer_Widget::getRotation( GLfloat& rotationAngle,
148                                    GLfloat& rotationCenterX,
149                                    GLfloat& rotationCenterY,
150                                    GLfloat& rotationCenterZ )
151 {
152     rotationAngle = myRotationAngle;
153     rotationCenterX = myRotationCenterX;
154     rotationCenterY = myRotationCenterY;
155     rotationCenterZ = myRotationCenterZ;
156 }
157
158 //=======================================================================
159 // Function: setRotation
160 // Purpose :
161 //=======================================================================
162 void GLViewer_Widget::setRotation( GLfloat rotationAngle,
163                                    GLfloat rotationCenterX,
164                                    GLfloat rotationCenterY,
165                                    GLfloat rotationCenterZ )
166 {
167     myRotationAngle = rotationAngle;
168     myRotationCenterX = rotationCenterX;
169     myRotationCenterY = rotationCenterY;
170     myRotationCenterZ = rotationCenterZ;
171 }
172
173 //=======================================================================
174 // Function: setBackground
175 // Purpose :
176 //=======================================================================
177 void GLViewer_Widget::setBackground( QString filename )
178 {
179     
180     //get image
181     QImage buf;
182     if ( !filename.isEmpty() && buf.load( filename ) ) 
183     {  // Load first image from file
184         isLoadBackground = true;
185         myBackgroundFile = filename;
186
187         myIW = buf.width();
188         myIH = buf.height();
189
190         myBackgroundSize = 64;
191         while( myBackgroundSize < myIW || myBackgroundSize < myIH)
192             myBackgroundSize = myBackgroundSize * 2;
193
194         GLubyte* pixels = new GLubyte[myBackgroundSize * myBackgroundSize * 4];
195
196         for( int i = 0; i < myBackgroundSize; i++ )
197         {            
198             for( int j = 0; j < myBackgroundSize; j++ )
199             {
200                 if( j < myIW && i < myIH )
201                 {
202                     pixels[i * myBackgroundSize * 4 + j * 4] = (GLubyte)qRed( buf.pixel(j,myIH - i - 1) );
203                     pixels[i * myBackgroundSize * 4 + j * 4 + 1]= (GLubyte)qGreen( buf.pixel(j,myIH - i - 1) );
204                     pixels[i * myBackgroundSize * 4 + j * 4 + 2] = (GLubyte)qBlue( buf.pixel(j,myIH - i - 1) );
205                 }
206                 else
207                 {
208                     pixels[i * myBackgroundSize * 4 + j * 4] = (GLubyte)0;
209                     pixels[i * myBackgroundSize * 4 + j * 4 + 1] = (GLubyte)0;
210                     pixels[i * myBackgroundSize * 4 + j * 4 + 2] = (GLubyte)0;
211                 }                
212                 pixels[i * myBackgroundSize* 4 + j * 4 +  3] = (GLubyte)255;
213             }
214         }
215
216         //initialize texture
217         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
218         glGenTextures(1, &texName);
219         glBindTexture(GL_TEXTURE_2D, texName);
220         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
221         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
222         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, myBackgroundSize , myBackgroundSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
223             pixels);
224
225         delete[] pixels;        
226     }
227 }
228
229 //=======================================================================
230 // Function: addToolTip
231 // Purpose :
232 //=======================================================================
233 void GLViewer_Widget::addToolTip( QString theString, QRect theRect )
234 {
235     myToolTipRect = theRect;
236     QToolTip::add( this, myToolTipRect, theString );
237 }
238
239 //=======================================================================
240 // Function: removeToolTip
241 // Purpose :
242 //=======================================================================
243 void GLViewer_Widget::removeToolTip()
244 {
245     QToolTip::remove( this, myToolTipRect );
246 }
247
248 //=======================================================================
249 // Function: initializeGL
250 // Purpose :
251 //=======================================================================
252 void GLViewer_Widget::initializeGL()
253 {
254     setAutoBufferSwap( true );
255
256     glShadeModel(GL_FLAT);
257     
258     //get image
259     QImage buf; 
260     QString aPicturePath = getenv("GLViewer__Background_Picture");
261     
262     if ( !aPicturePath.isEmpty() && buf.load( aPicturePath ) ) 
263     {  // Load first image from file
264         isLoadBackground = true;
265         setBackground( aPicturePath );       
266         
267     }
268     
269     else
270         isLoadBackground = false;
271 }
272
273 //=======================================================================
274 // Function: paintGL
275 // Purpose :
276 //=======================================================================
277 void GLViewer_Widget::paintGL()
278 {
279     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
280     
281     glMatrixMode( GL_MODELVIEW );
282     glLoadIdentity();    
283
284     glRotatef( myRotationAngle, myRotationCenterX, myRotationCenterY, myRotationCenterZ );
285     glScalef( myXScale, myYScale, myZScale );
286     glTranslatef( myXPan, myYPan, myZPan );
287     
288     if( isLoadBackground )
289     {
290         glEnable(GL_TEXTURE_2D);
291         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
292         glBindTexture(GL_TEXTURE_2D, texName);
293         glBegin(GL_QUADS);
294
295         glTexCoord2f( 0.0, 0.0); glVertex3f( -myIW/2, -myIH/2, 0.0);    
296         glTexCoord2f( 0.0, (float)myIH/myBackgroundSize ); glVertex3f( -myIW/2, myIH/2, 0.0);
297         glTexCoord2f( (float)myIW/myBackgroundSize, (float)myIH/myBackgroundSize ); glVertex3f( myIW/2, myIH/2, 0.0);
298         glTexCoord2f( (float)myIW/myBackgroundSize, 0.0); glVertex3f( myIW/2, -myIH/2, 0.0);
299         
300         glEnd();
301         glFlush();
302         glDisable(GL_TEXTURE_2D);
303
304         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
305     }
306
307
308     GLViewer_Grid* grid = myViewPort->getGrid();
309     if( grid )
310         grid->draw();
311
312     GLViewer_Viewer2d* v = ( GLViewer_Viewer2d* )getViewPort()->getViewFrame()->getViewer();
313     if( !isExportMode )
314         v->updateDrawers( GL_FALSE, myXScale, myYScale );
315     else
316         v->repaintView( getViewPort()->getViewFrame() );
317 }
318
319 //=======================================================================
320 // Function: resizeGL
321 // Purpose :
322 //=======================================================================
323 void GLViewer_Widget::resizeGL( int w, int h )
324 {
325
326   if( h < 1 ) h = 1;
327   if( w < 1 ) w = 1;
328   glViewport( 0, 0, w, h);
329
330   if( myStart )
331   {
332     myWidth = w;
333     myHeight = h;
334     myStart = GL_FALSE;
335   }
336
337   myViewPort->initResize( w, h );
338
339   glMatrixMode( GL_PROJECTION );
340   glLoadIdentity();
341   GLfloat w_c = w / 2., h_c = h / 2.; 
342
343   gluOrtho2D( -w_c, w_c, -h_c, h_c ); 
344
345   glMatrixMode( GL_MODELVIEW );
346   glLoadIdentity(); 
347 }
348
349 //=======================================================================
350 // Function: exportRepaint
351 // Purpose :
352 //=======================================================================
353 void GLViewer_Widget::exportRepaint()
354 {
355     isExportMode = true;
356
357     paintGL();
358
359     isExportMode = false;
360 }
361
362 //=======================================================================
363 // Function: paintEvent
364 // Purpose :
365 //=======================================================================
366 void GLViewer_Widget::paintEvent( QPaintEvent* e )
367 {
368   QApplication::sendEvent( myViewPort, e );
369 }
370
371 //=======================================================================
372 // Function: mouseMoveEvent
373 // Purpose :
374 //=======================================================================
375 void GLViewer_Widget::mouseMoveEvent( QMouseEvent* e )
376 {
377   QApplication::sendEvent( myViewPort, e );
378 }
379
380 //=======================================================================
381 // Function: mousePressEvent
382 // Purpose :
383 //=======================================================================
384 void GLViewer_Widget::mousePressEvent( QMouseEvent* e )
385 {
386   QApplication::sendEvent( myViewPort, e );
387 }
388
389 //=======================================================================
390 // Function: mouseReleaseEvent
391 // Purpose :
392 //=======================================================================
393 void GLViewer_Widget::mouseReleaseEvent( QMouseEvent* e )
394 {
395   QApplication::sendEvent( myViewPort, e );
396 }
397
398 //=======================================================================
399 // Function: enterEvent
400 // Purpose :
401 //=======================================================================
402 void GLViewer_Widget::enterEvent( QEvent* e )
403 {
404   updateGL();
405 }
406
407 //=======================================================================
408 // Function: leaveEvent
409 // Purpose :
410 //=======================================================================
411 void GLViewer_Widget::leaveEvent( QEvent* e )
412 {
413   updateGL();
414 }
415
416
417 //=======================================================================
418 //! Function: hex
419 //! Purpose : Returns the hex code of digit < 16
420 //=======================================================================
421 inline char hex( uchar c )
422 {
423   if( c<=9 )
424     return '0'+c;
425   else if( c < 16 )
426     return 'a' + c - 10;
427
428   return ' ';
429 }
430
431 //=======================================================================
432 //! Function: AddImagePart
433 //! Purpose : Translates path of image to PS format
434 /*! Image inside rectangle from w1 to w2 and from h2 to h1*/
435 //=======================================================================
436 void AddImagePart( QFile& hFile, QImage& image, int w1, int w2, int h1, int h2, 
437                    GLViewer_CoordSystem* aViewerCS, GLViewer_CoordSystem* aPSCS, 
438                    double a, double b, double c, double d, double dw, double dh )
439 {
440   if( aViewerCS && aPSCS )
441   {       
442     double width = w2-w1+1, height = h2-h1+1;
443     QString aBuffer = "", temp = "%1 %2 8 [ %3 %4 %5 %6 %7 %8 ]\n";
444     aBuffer += temp.arg( width ).arg( height ).
445                         arg( a ).arg( b ).arg( c ).arg( d ).
446                         arg( dw ).arg( dh );
447     aBuffer += "<\n";   
448     
449     char line[81]; line[80] = '\0'; int cur_index = 0;
450     int full = 0;
451     for( int i=h2; i>=h1; i-- )
452     {           
453       uchar* theCurLine = image.scanLine( i ), cur;
454       for( int j=w1; j<=w2; j++ )
455         for( int k=0; k<3; k++ )
456         {
457           cur = *(theCurLine+4*j+2-k);
458           *(line+cur_index) = hex( cur/16 ); //HI
459           *(line+cur_index+1) = hex( cur%16 ); //LO
460           full++;
461           cur_index+=2;
462           if( cur_index>=80 )
463           {
464             aBuffer += line;
465             aBuffer += "\n";
466             cur_index = 0;
467           }
468         }           
469     }
470     
471     aBuffer += "> false 3 colorimage\n\n";
472
473     hFile.writeBlock( aBuffer.ascii(), aBuffer.length() );
474   }
475 }
476
477 //=======================================================================
478 // Function: getBackgroundRectInViewerCS
479 // Purpose :
480 //=======================================================================
481 void GLViewer_Widget::getBackgroundRectInViewerCS( double& left, double& top, double& right, double& bottom )
482 {
483   left = -myIW/2; right = myIW/2; 
484   top = myIH/2; bottom = -myIH/2;
485 }
486
487 //=======================================================================
488 // Function: translateBackgroundToPS
489 // Purpose :
490 //=======================================================================
491 void GLViewer_Widget::translateBackgroundToPS( QFile& hFile, GLViewer_CoordSystem* aViewerCS, GLViewer_CoordSystem* aPSCS )
492 {
493     QImage buf; 
494
495     if( aViewerCS && aPSCS && isLoadBackground && buf.load( myBackgroundFile ) )
496     {       
497         double a, b, c, d, dx, dy; //The preparation of transformation matrix
498
499         double width = buf.width(), height = buf.height();
500
501         double left, top, right, bottom;
502         getBackgroundRectInViewerCS( left, top, right, bottom );
503
504         double aax = left,  aay = bottom,
505                bbx = right, bby = bottom,
506                ccx = left,  ccy = top;             
507
508         aViewerCS->transform( *aPSCS, aax, aay );
509         aViewerCS->transform( *aPSCS, bbx, bby );
510         aViewerCS->transform( *aPSCS, ccx, ccy );       
511
512         a = ( bbx - aax ) / width;
513         b = ( ccx - aax ) / height;
514         c = ( bby - aay ) / width;
515         d = ( ccy - aay ) / height;
516
517         //Now we must find invert matrix 
518         double det = a*d-b*c,
519                newa = d/det,
520                newb = -c/det,
521                newc = -b/det,
522                newd = a/det;
523
524         a = newa; b = newb; c = newc; d = newd;
525
526         dx = -(a*aax+c*aay);
527         dy = -(b*aax+d*aay); //according to PS specification of coordinate transformation
528         
529         const int max = 133000; //The maximum length of string in PS
530         int dh = int( floor( double( max ) / ( 3.0*2.0*width ) ) );
531         for( int k=buf.height()-1; k>=0; k-=dh )
532             AddImagePart( hFile, buf, 0, buf.width()-1, QMAX( k-dh+1, 0 ), k,
533                           aViewerCS, aPSCS, a, b, c, d, dx, dy-(buf.height()-1-k) );
534     }
535 }
536
537 //=======================================================================
538 //! Function: DecodeScanLine
539 //! Purpose : Translate image line with one color depth to line wiht other depth
540 //=======================================================================
541 void DecodeScanLine( int width, uchar* dest, int dest_depth, uchar* source, int source_depth )
542 {
543 #ifndef WIN32
544 typedef unsigned int WORD;
545 #endif
546
547     int aSize = width*dest_depth,
548         dw = aSize % 8;
549
550     if( dw )
551         aSize+=dw;
552
553     if( dest_depth==source_depth )
554         memcpy( dest, source, aSize/8 );
555     else
556     {
557         double r, g, b; WORD color;
558         for( int i=0; i<width; i++ )
559         {
560             color = 0;
561             switch( source_depth )
562             {
563                 case 16:
564                     memcpy( &color, source + 2*i, 2 );
565                     b = double( color & 0x001F ) / 31.0;
566                     g = double( ( color & 0x07E0 ) >> 5 ) / 63.0;
567                     r = double( ( color & 0xF800 ) >> 11 ) / 31.0;
568                     break;
569                 case 24: 
570                     b = double( *(source + 3*i) ) / 255.0;
571                     g = double( *(source + 3*i+1) ) / 255.0;
572                     r = double( *(source + 3*i+2) ) / 255.0;
573                     break;
574                 case 32:
575                     b = double( *(source + 4*i) ) / 255.0;
576                     g = double( *(source + 4*i+1) ) / 255.0;
577                     r = double( *(source + 4*i+2) ) / 255.0;
578                     break;
579             }
580             switch( dest_depth )
581             {
582                 case 16:
583                     color = WORD(b*31.0);
584                     color += (WORD(g*63.0)<<5);
585                     color += (WORD(r*31.0)<<11);
586                     memcpy( dest + 2*i, &color, 2 );
587                     break;
588                 case 24:
589                     *( dest + 3*i ) = (uchar)(255*b);
590                     *( dest + 3*i+1 ) = (uchar)(255*g);
591                     *( dest + 3*i+2 ) = (uchar)(255*r);
592                     break;
593                 case 32:
594                     *( dest + 4*i ) = (uchar)(255*b);
595                     *( dest + 4*i+1 ) = (uchar)(255*g);
596                     *( dest + 4*i+2 ) = (uchar)(255*r);
597                     *( dest + 4*i+3 ) = 0;
598                     break;
599             }
600         }
601     }
602 }
603
604 //=======================================================================
605 // Function: translateBackgroundToEMF
606 // Purpose :
607 //=======================================================================
608 #ifdef WIN32
609 void GLViewer_Widget::translateBackgroundToEMF( HDC dc, GLViewer_CoordSystem* aViewerCS, GLViewer_CoordSystem* aEMFCS )
610 {
611     QImage buf; 
612
613     if( aViewerCS && aEMFCS && isLoadBackground && buf.load( myBackgroundFile ) )
614     {       
615         double left, top, right, bottom;
616         getBackgroundRectInViewerCS( left, top, right, bottom );
617
618         double aRot = aViewerCS->getRotation();
619
620         double lx = left, ly = top;
621         aViewerCS->transform( *aEMFCS, lx, ly );
622
623         aViewerCS->setRotation( 0.0 ); //we switch off the rotation of CS
624
625         aViewerCS->transform( *aEMFCS, left, top );
626         aViewerCS->transform( *aEMFCS, right, bottom );
627         
628         int w = buf.width(), 
629             h = buf.height();
630
631         HDC aScrDC = GetDC( 0 );
632         HDC aCompDC = CreateCompatibleDC( aScrDC );
633         HBITMAP aBMP = CreateCompatibleBitmap( aScrDC, w, h );
634
635         BITMAP aBitInfo;
636         GetObject ( aBMP, sizeof(BITMAP), &aBitInfo );
637         int depth = aBitInfo.bmBitsPixel; //how many bits represent a color of one pixel
638
639         int aLineSize = w*depth;
640         int dw = aLineSize % 32; //scanline word aligning
641
642         if( dw )
643             aLineSize += 32-dw;
644
645         aLineSize /= 8;
646
647         BYTE* bits = new BYTE[aLineSize*h];
648         memset( bits, 0, aLineSize*h );
649         uchar* aLine = NULL;
650
651         for( int i=0; i<h; i++ )
652         {
653             aLine = buf.scanLine( i );
654             DecodeScanLine( w, bits+aLineSize*i, depth, aLine, buf.depth() );
655         }
656
657         SetBitmapBits( aBMP, aLineSize*h, bits );
658
659         HGDIOBJ old = SelectObject( aCompDC, aBMP ); 
660
661         XFORM aTrans;
662         GetWorldTransform( dc, &aTrans );
663         XFORM aRotTrans = aTrans;
664         double a = aRotTrans.eM11, 
665                b = aRotTrans.eM12, 
666                c = aRotTrans.eM21, 
667                d = aRotTrans.eM22;
668
669         aRotTrans.eM11 = a*cos( aRot )-b*sin( aRot ); //we multiply the current matrix with the rotation matrix 
670         aRotTrans.eM12 = a*sin( aRot )+b*cos( aRot );
671         aRotTrans.eM21 = c*cos( aRot )-d*sin( aRot );
672         aRotTrans.eM22 = c*sin( aRot )+d*cos( aRot );
673
674         a = aRotTrans.eM11; 
675         b = aRotTrans.eM12; 
676         c = aRotTrans.eM21; 
677         d = aRotTrans.eM22;
678
679         double det = a*d-b*c, //now we find the invert matrix 
680                newa = d/det,
681                newb = -c/det,
682                newc = -b/det,
683                newd = a/det;
684
685         a = newa; b = newb; c = newc; d = newd;
686
687         aRotTrans.eDx = lx -(a*left+c*top); //we find the dx and dy translating (left,top)->(lx,ly) -                                           
688         aRotTrans.eDy = ly -(b*left+d*top); //the real image of left-top corner of picture
689
690         SetWorldTransform( dc, &aRotTrans );
691         int res = StretchBlt( dc, left, top, right-left, bottom-top, aCompDC, 0, 0, w, h, SRCCOPY );
692         SetWorldTransform( dc, &aTrans );
693
694         SelectObject( aCompDC, old );
695
696         ReleaseDC( 0, aScrDC );
697         DeleteDC( aCompDC );
698         DeleteObject( aBMP );
699         delete[] bits;
700
701         aViewerCS->setRotation( aRot );
702     }
703 }
704 #endif