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: , , , , , ,

Friday, August 03, 2007

Finally an update !

It has been a while since I have updated this blog. Well the primary reason is, that I was working on the new GUI system and at that time there wasn't anything that could be shown.

But it is progressing and I can show a few early screenshots.

The old GUI systems worked fairly well, but had a few flaws. I tried to avoid these problems and add more functionality this time.

One of the major problem in the old GUI was the rendering system. The GUI skin was one big file. It included description of how to render each widget and for each different state. There were generally 4 states: normal , disabled , hover , clicked. Each state is drawn by GL_POLYGON and all the points should be specified directly with (Color,Texture coordinate,position on the 2d screen and anchor).

The new GUI however tries to minimize the amount of data that needs to be written. This is achieved by creating one stroke object that would later be used to draw different parts of many widgets, but it only needs to be defined once.

The widget shape is created from the list of these strokes. The stroke itself is made from many 2d primitive shapes like: line , rectangle , round rectangle , circle , ellipse , arc and bezier. The amount of the data that needs to be written to define a stroke is minimum.

Here is an example of the stroke created from 2 bezier curves:



Each bezier curve is defined by 4 points. The GUI also allows to change the quality of shapes.

The previous shape was created from 16 iterations this is created from 8:


Each shape except line and rectangle can have different quality versions.

One of the most important additions to the GUI rendering system is the material system. The GUI uses the same material system that is used in any other engine part.
This makes it possible to create all sorts of animation effects. The texture coordinates , gradient and alpha values are generated automatically:



One of the most annoying issues when making GUI systems, especially with OpenGL is the lack of good line drawing functionality. The standard line drawing implementation is really limited. Not only it doesn't guarantee some line widths to be available, but the width itself is related directly to pixel width. Which makes it looks different on different resolutions. The other problem is antialiasing of the line. It is widely supported by most graphic cards this day, however their implementation depends on the graphic card manufacturer driver version, and can produce different results.

The line drawing system that I am using here is created by two 4 vertex quads. Original drawing with one 4 vertex quad produced really noticeable jagged lines. However by adding additional quad and using blending, made it look really well, without almost any performance impact. It would also work on any OpenGL implementation and on any resolution the same correct way:



I would probably make some more posts soon, if I manage to get something new working :)

Labels: , , , , ,

Saturday, February 17, 2007

A little progress with PhysX integration

I would like to show a little progress I am having with PhysX.

This video shows the integration of PhysX with BSP map. Currently no collision detection is implemented for curved patches.

The BSP collision detection mesh is created using nxTriangleMesh, which is generated for the whole level. I had some problems with overall performance, caused by the debug information collection, but it was solved.



Direct Link

Labels: , , , , ,

Thursday, February 08, 2007

PhysX sample

 Just want to show a little sample video of the PhysX demo I recently made. This is just based on some of the tutorials provided with PhysX, and it is not integrated with the engine yet, but I am looking forward for that.




Direct link

Labels: , ,

Monday, February 05, 2007

New Audio Module

For the past few weeks I have been working on the new audio engine.

The SDL_Mixer is a great library and I just love its simplicity and straightforward API code. However there is one significant feature missing. It is 3D sound support. Off course it could be faked, but I decided to give OpenAL a try.

The library itself is a well-known standard right now for 3D Sound audio and allot of new games are using it for their audio support.

The library itself is fairly low level. Its API is somewhat similar to the OpenGL. The API has 3 major parts:

1. Device
2. Buffer
3. Source

The device is an output device that is available on the machine. On my machine there are 2 devices:

1. Software
1. Hardware

It is possible to select either of them. On some other machines another devices are available. Usually device can have additional features included, which can be requested through the extension system. Quite similar to OpenGL.

When the device is created the OpenAL allows you to request sources to be played on that device. Source is like a point in space where sound is playing. It is possible to specify allot of different properties to the source such as position , velocity , volume (Gain) and more. There is also a Listener it is exactly what it is named like . There can be only one Listener and this Listener is who can "hear" the sounds.

The sound itself is loaded into buffer or multiple buffers, which are then assigned to the source and OpenAL starts playing them.

This is probably all it can generally do. But the best part off course is the 3d positioning and the doppler shift. The extensions are also one cool thing as it allows to create plenty effects to simulate different audio environments.


For the audio file I decided to use ogg/vorbis. It is royalty free audio format which has become quite popular now. I decided to use ogg instead of regular .wav files to save memory space. Off course it would add additional impact on the processor but this wouldn't be so hard I think.


So generally I have integrated all of this into one easy to use and intuitive audio engine.

The following flow chart or class structure illustrates the whole picture:



There is one singleton factory called AudioManager class. Its primary job is to manage all the audio data, creation and destruction. Throught it sources and streams are requested.

The AudioManager contains the StreamList class which is collection of Streams.
Each Stream represents Ogg file (either in the loaded memory or on the local hard drive). Stream itself allows to read from this file and contains reference counter to automatically destroy itself when required.

There is also an AudioThread. Its primary job is to continuously stream the Ogg files fill the Audio buffers to the source.

AudioThread manages 4 lists :

1. List of Passive 3d sources
2. List of Active 3d sources
3. List of Passive 1d sources
4. List of Active 1d sources


Generally when the application starts a fixed number of sources is created. Usually 1-4 of 1D sources and 5-20 of 3D sources.

Now when any sound request the source for playing, then audio thread requests source from passive sources and if successful, puts it to the active sources and returns reference to the source. If not well it ignores the command then.

All the active sources are walked through by the AudioThread and get streamed.

The most important part is that only the AudioThread can change any of the list. So the requests are synchronized with a mutex.


Now about user Interface.

There are only 3 classes available :

1. Sound1D
2. Sound3D
3. Music


Sound1D is used for the stereo sounds, such as playing GUI elements or videos in the game. It contains all the required basic operation : Loading , Playing , Stopping , Pausing and so on. Each sound contains StreamReader. The StreamReader just holds the position on the stream that gets streamed. This is used to assure that any 2 sounds that are playing the same Stream wouldn't interfer with each other.

Sound3D is used for the in-game 3d sounds. It is important that the sounds used is mono. Otherwise it wouldn't play or cause not desirable side effects. Sound3D has the same functions as the Sound1D , but also has additional functions that specify position in the 3D space and velocity. I am also planning to add a binding option so it would automatically synchronize itself with physical engine.

Music is a singleton class. It is actually available only by calling through the AudioManager. Generally it holds Sound1D inside itself and calls its functions. The only difference is the volume control, that allows music to have different volume then just sounds.

This is not all the stuff that is implemented, but I just explained general idea, and saved it for my own future reference.

P.S. I am using gliffy for this diagram. Check it out, its cool !

Labels: , , , , , , , , , , ,