Monday, November 19, 2007

Line cap calculation

To continue my previous post, I would like to explain a little more about how the line cap is calculated.

Here is the order at which the line is rendered:

1. Line Start Cap calculation
2. Join vertex calculation n1
3. ...
4. Join vertex calculation n1 + joinNum
5. Line End Cap calculation


As you can see the cap starts the line and ends it. Because I am using GL_QUAD_STRIP then it is important to properly connect all the vertices together, and espcially make their total number base power of 4, because each quad consists of 4 points.

There are usually 3 different cap styles:

1. None (the cap vertexes are created at origin of the start/end point) [2 vertexes]


2. Square (the cap vertexes are created at the same origin but with an offset equal the the line width)[2 vertexes]



3. Round (the cap vertexes form half-arc at the origin of the start/end point)[2 + 2*iterations vertexes]


So The first thing that is required - the general cap function:

//Calculates cap for the line
void CalculateCap(  LineVertex v0,
                                   LineVertex v1,
                                   bool start ) //decides is this cap starts the line = true or ends = false
{

     float len = CalculateDistance( v0 , v1 );

     float dx1 = (v1.posY - v0.posY) / len;
     float dy1 = (v1.posX - v0.posX) / len;
     float dx2 = 0;
     float dy2 = 0;

     dx1 *= _size;
     dy1 *= _size;

    if( _capStyle != CapStyle_Round)
   {
        if( _capStyle == CapStyle_Square)
       {
            dx2 = dy1 ;
            dy2 = dx1 ;
       }

       if( start )
      {
           AddVertex( v0.posX + dx1 - dx2, v0.posY - dy1 - dy2);
           AddVertex( v0.posX - dx1 - dx2, v0.posY + dy1 - dy2);
      }
      else
     {
          //end of path
         AddVertex( v0.posX - dx1 - dx2, v0.posY + dy1 - dy2);
         AddVertex( v0.posX + dx1 - dx2, v0.posY - dy1 - dy2);
     }
  }
  else
  {

      //Converts radian result of atan2 to Deg we need for comaring later
       float subAngle = -(TRIG_RAD2DEG( atan2( dx1 , dy1 ) ) - 180);

     //simple but does check the direction of the line edge
      if( (subAngle > 90 )&&( subAngle <= 270 ) )      
      {           
             if( start )          
            {               
                    CalculateRound(   v0 ,                                              
                                                   dx1, -dy1 ,                                             
                                                  -dx1, dy1 ,                                             
                                                   v0.posX + dx1 ,                                             
                                                   v0.posY - dy1 ,                                             
                                                   true );         
           }        
          else               
               {                      
                   CalculateRound(  v0 ,                                                   
                                                  dx1, -dy1 ,                                                 
                                                 -dx1, dy1 ,                                                  
                                                 v0.posX - dx1 ,                                                  
                                                 v0.posY + dy1 ,                                                 
                                                  true );               
               }    
      }   
     else        
          {          
                    if( start )          
                    {                  
                                 CalculateRound( v0 ,                                               
                                                             -dx1, dy1 ,                                                 
                                                              dx1, -dy1 ,                                               
                                                              v0.posX - dx1 ,                                                
                                                              v0.posY + dy1 ,                                                
                                                              false );         
                     }         
                     else              
                          {                           
                                  CalculateRound( v0 ,                                                       
                                                              -dx1, dy1 ,                                                        
                                                               dx1, -dy1 ,                                                         
                                                               v0.posX + dx1 ,                                                         
                                                              v0.posY - dy1 ,                                                         
                                                               false );             
                          }        
             }      
      }
}


This general function finds the result vertexes this way:

1. Finds relative position of the initial vertexes. Exactly the same way as it was made with "None" line join.

2.Now depending on the Cap style:     

  None     - Just use these calculated vertexes and add them to the render buffer.     
  Square  - Apply line width multiplier and add them to the render buffer.     
   Round  - This is more complicated:     

For round join the renderer needs to calculate arc:

void CalculateRound(    
                                         LineVertex v0,        //center of the line join                                        
                                         float dx1, float dy1, //the start point                                        
                                         float dx2, float dy2, //the end point                                       
                                         float middleX ,                                        
                                         float middleY ,   //The middle point for creating correct GL_QUAD vertex                             bool reverse) //use reverse incase of inner join

{            

          float a1 = TRIG_RAD2DEG( atan2( dy1 , dx1 ));            
          float a2 = TRIG_RAD2DEG( atan2( dy2 , dx2 ));            

          float angleStep = (a1 - a2) ;            //number of iterations            
          angleStep = angleStep / (iterations + 1) ;            //starting angle            
          float cangle = 0;          


           if( reverse )          
          {                
                     //INNER JOIN               
                     AddVertex( middleX , middleY );               
                     AddVertex( v0.posX - dx2 , v0.posY - dy2);              

                      a2 += 180;              
                      cangle = a2 ;             

                    for(int i = 0; i < iterations ; i++)            
                   {                    
                            cangle += angleStep;                   //Also put the middle point for the GL to correctly link other GL_QUAD later                   
                          AddVertex( middleX , middleY );                   //find the rotated point                           AddVertex(                                         
                                             _size * trig.Cos(cangle) + v0.posX ,                                         
                                             _size * trig.Sin(cangle) + v0.posY );            
                   }           

                         AddVertex( middleX , middleY );            
                         AddVertex( v0.posX - dx1, v0.posY - dy1);       
          }       
          else      
               {              
                       //OUTER JOIN              
                      AddVertex( v0.posX + dx1 , v0.posY + dy1);              
                      AddVertex( middleX , middleY );             //change the direction of angle iteration            
                      angleStep *= -1;            
                      cangle = a1 ;           

                      for(int i = 0; i < iterations ; i++)          
                     {                  
                            cangle += angleStep;                 //find the rotated point                

                            AddVertex(                                     
                                              _size * trig.Cos(cangle) + v0.posX ,                                     
                                             _size * trig.Sin(cangle) + v0.posY );               //Also put the middle point for the GL to correctly link other GL_QUAD later               

                           AddVertex( middleX , middleY );             
                   }            

                            AddVertex( v0.posX + dx2, v0.posY + dy2);           
                            AddVertex( middleX , middleY );        
           }
 }


The arc calculation is not that hard. For this we need to have the center point, the start and end point. The middleX and middleY are used as an additional point to connect quad with.

We also need to find the start angle from where to start iteration and the end angle when to end.

Each iterations adds additional 2 vertexes. If you look closely for the iteration code, you would see that it generates minimum 4 vertexes. The first 2 vertexes are actually at the same point, they start the cap, then there are iteration vertexes and at the end 2 more for opening the next 2 vertexes

The order of drawing for 3 iterations is shown next. This is for the start cap with values:

CalculateRound( v0 ,                              
                              dx1, -dy1 ,                              
                              -dx1, dy1 ,                              
                              v0.posX + dx1 ,                              
                              v0.posY - dy1 ,                              
                              true );

Initial state:


1. First 2 vertexes and iteration 0


2. Iteration 1


3. Iteration 2


4. And now the end 2 vertexes are calculated



This is probably all that is required for rendering line cap styles.

Labels: , , , , , ,

1 Comments:

At 4:57 AM, Anonymous GenesisCEO said...

hi~nice blog, love your site~visit again,c ya

 

Post a Comment

Links to this post:

Create a Link

<< Home