使用XNA绘制三次贝赛尔曲线

  以前有人发表过类似的文章,是用Texture2D模拟贝赛尔曲线 ,而本文是基于顶点绘制的。
  在XNA中,使用DrawUserPrimitives方法可以绘制直线,但是没有直接绘制贝塞尔曲线的方法。其实绘制贝塞尔曲线就是绘制一组与贝赛尔曲线近似的折线段。

  面向对象编程,首先定义贝赛尔曲线类。三次贝赛尔曲线由两端的锚点和中间两个控制点组成,没什么好说的,上代码:

Bezier.cs
using  System.Collections.Generic;
using  Microsoft.Xna.Framework;

namespace  DrawBezier
{
    
///   <summary>
    
///      定义一段三次贝塞尔曲线。
    
///   </summary>
     public   class  Bezier
    {
        
///   <summary>
        
///      储存构成该贝赛尔曲线的点的对应顶点结构。
        
///   </summary>
        List < Vector2 >  points;

        
///   <summary>
        
///      获取或设置贝赛尔曲线的第一个锚点。
        
///   </summary>
         public  Vector2 Anchor1
        {
            
get
            {
                
return  anchor1;
            }
            
set
            {
                anchor1 
=  value;
                needUpdate 
=   true ;
            }
        }
        Vector2 anchor1;

        
///   <summary>
        
///      获取或设置贝赛尔曲线的第二个锚点。
        
///   </summary>
         public  Vector2 Anchor2
        {
            
get
            {
                
return  anchor2;
            }
            
set
            {
                anchor2 
=  value;
                needUpdate 
=   true ;
            }
        }
        Vector2 anchor2;

        
///   <summary>
        
///      获取或设置贝赛尔曲线的第一个控制点。
        
///   </summary>
         public  Vector2 Control1
        {
            
get
            {
                
return  control1;
            }
            
set
            {
                control1 
=  value;
                needUpdate 
=   true ;
            }
        }
        Vector2 control1;

        
///   <summary>
        
///      获取或设置贝赛尔曲线的第二个控制点。
        
///   </summary>
         public  Vector2 Control2
        {
            
get
            {
                
return  control2;
            }
            
set
            {
                control2 
=  value;
                needUpdate 
=   true ;
            }
        }
        Vector2 control2;

        
///   <summary>
        
///      用于判断曲线的作用点是否改变。
        
///   </summary>
         bool  needUpdate  =   false ;

        
///   <summary>
        
///      用指定参数创建贝赛尔曲线的新实例。
        
///   </summary>
        
///   <param name="anchor1"> 指定贝赛尔曲线的第一个锚点。 </param>
        
///   <param name="control1"> 指定贝赛尔曲线的第一个控制点。 </param>
        
///   <param name="control2"> 指定贝赛尔曲线的第二个控制点。 </param>
        
///   <param name="anchor2"> 指定贝赛尔曲线的第二个锚点。 </param>
         public  Bezier(Vector2 anchor1, Vector2 control1, Vector2 control2, Vector2 anchor2)
        {
            
this .Anchor1  =  anchor1;
            
this .Anchor2  =  anchor2;
            
this .Control1  =  control1;
            
this .Control2  =  control2;
        }
    }
}

  设贝塞尔曲线的作用点分别为P1(第一个锚点)、P2(第一个控制点)、P3(第二个控制点)、P4(第二个锚点)。首先确定P1和P2、P2和P3、P3和P4的中心点,分别设为P12、P23、P34;然后找到P12和P23、P23和P34的中心点,设为P123、P234;这两个点的中心点P为该贝塞尔曲线上的一点(这是贝塞尔曲线的一个重要性质,其原理网上到处都是)。此点将原贝塞尔曲线一分为二,生成两段新的贝塞尔曲线。

  根据这个原理,可以递归拆分贝赛尔曲线直到生成的曲线与直线近似。判断是否近似的标准就是:新生成贝塞尔曲线的两个控制点到经过两锚点的直线的距离小于指定容限(注:容限一般设为0.1414)。

  代码如下: 

Bezier.cs方法
         ///   <summary>
        
///      用指定参数创建贝赛尔曲线的新实例。
        
///   </summary>
        
///   <param name="anchor1"> 指定贝赛尔曲线的第一个锚点。 </param>
        
///   <param name="control1"> 指定贝赛尔曲线的第一个控制点。 </param>
        
///   <param name="control2"> 指定贝赛尔曲线的第二个控制点。 </param>
        
///   <param name="anchor2"> 指定贝赛尔曲线的第二个锚点。 </param>
         public  Bezier(Vector2 anchor1, Vector2 control1, Vector2 control2, Vector2 anchor2)
        {
            
this .Anchor1  =  anchor1;
            
this .Anchor2  =  anchor2;
            
this .Control1  =  control1;
            
this .Control2  =  control2;
        }

        
///   <summary>
        
///      获取该贝赛尔曲线的近似折线的点集。
        
///   </summary>
        
///   <returns> 该贝赛尔曲线的近似折线的点集。 </returns>
         public  List < Vector2 >  GetPoints()
        {
            
if  (needUpdate)
            {
                points 
=   new  List < Vector2 > ();
                points.Add(
this .Anchor1);
                CubicBezierToPoints(
this .Anchor1,  this .Control1,  this .Control2,  this .Anchor2, points);
                needUpdate 
=   false ;
            }
            
return  points;
        }

        
///   <summary>
        
///      将指定参数的贝赛尔曲线转换为近似折线的点集。
        
///   </summary>
        
///   <param name="p0"> 指定贝赛尔曲线的第一个锚点。 </param>
        
///   <param name="p1"> 指定贝赛尔曲线的第二个控制点。 </param>
        
///   <param name="p2"> 指定贝赛尔曲线的第一个控制点。 </param>
        
///   <param name="p3"> 指定贝赛尔曲线的第二个锚点。 </param>
        
///   <param name="points"> 转换后的近似折线的点集。 </param>
         void  CubicBezierToPoints(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, List < Vector2 >  points)
        {
            
if  (Distance(p1, p0, p3)  <   0.02   &&  Distance(p2, p0, p3)  <   0.02 )
            {
                points.Add(p3);
            }
            
else
            {
                Vector2 p01 
=   new  Vector2((p0.X  +  p1.X)  /   2 , (p0.Y  +  p1.Y)  /   2 );
                Vector2 p12 
=   new  Vector2((p1.X  +  p2.X)  /   2 , (p1.Y  +  p2.Y)  /   2 );
                Vector2 p23 
=   new  Vector2((p2.X  +  p3.X)  /   2 , (p2.Y  +  p3.Y)  /   2 );

                Vector2 p012 
=   new  Vector2((p01.X  +  p12.X)  /   2 , (p01.Y  +  p12.Y)  /   2 );
                Vector2 p123 
=   new  Vector2((p12.X  +  p23.X)  /   2 , (p12.Y  +  p23.Y)  /   2 );

                Vector2 p 
=   new  Vector2((p012.X  +  p123.X)  /   2 , (p012.Y  +  p123.Y)  /   2 );

                CubicBezierToPoints(p0, p01, p012, p, points);
                CubicBezierToPoints(p, p123, p23, p3, points);
            }
        }

        
///   <summary>
        
///      计算指定点 p 到指定直线 ab 的距离的平方。
        
///   </summary>
        
///   <param name="p"> 指定点 p。 </param>
        
///   <param name="a"> 指定直线的第一个点 a。 </param>
        
///   <param name="b"> 指定直线的第二个点 b。 </param>
        
///   <returns> 点 p 到直线 ab 的距离的平方。 </returns>
         float  Distance(Vector2 p, Vector2 a, Vector2 b)
        {
            
float  paX  =  p.X  -  a.X;
            
float  paY  =  p.Y  -  a.Y;

            
float  baX  =  b.X  -  a.X;
            
float  baY  =  b.Y  -  a.Y;

            
float  pa2  =  paX  *  paX  +  paY  *  paY;
            
float  ba2  =  baX  *  baX  +  baY  *  baY;

            
float  apab  =  paX  *  baX  +  paY  *  baY;
            
float  apab2  =  apab  *  apab;

            
return  pa2  -  apab2  /  ba2;
        }

   得到这组点集后可以做很多事,这里提供我自己的绘制方法,以供参考。

  定义简单的顶点结构:VertexPosition2D 

 

VertexPosition2D.cs
using  Microsoft.Xna.Framework;
using  Microsoft.Xna.Framework.Graphics;

namespace  DrawBezier
{
    
public   struct  VertexPosition2D
    {
        
public  Vector2 Position;
        
public   static   readonly   int  SizeInBytes  =   sizeof ( float *   2 ;

        
public   static   readonly  VertexElement[] VertexElements  =  
        {
            
new  VertexElement( 0 0 , VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.Position,  0 ),
        };

        
public  VertexPosition2D(Vector2 position)
        {
            Position 
=  position;
        }
    }
}

    编写简单的绘制直线的Shader:

 

Line.fx
float4 Color;

float4 VS_Main(float2 Position : POSITION0) : POSITION0
{
    
return  float4(Position.xy, 0.5 , 1 );
}

float4 PS_Main(float2 Position : POSITION0) : COLOR0
{
    
return  Color;
}

technique Technique1
{
    pass Pass1
    {
        VertexShader 
=  compile vs_1_1 VS_Main();
        PixelShader 
=  compile ps_1_1 PS_Main();
    }
}

    在游戏主程序中运用:

 

Game1.cs
using  System;
using  System.Collections.Generic;
using  Microsoft.Xna.Framework;
using  Microsoft.Xna.Framework.Graphics;

namespace  DrawBezier
{
    
///   <summary>
    
///      测试贝塞尔曲线。
    
///   </summary>
     public   class  Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;

        Effect lineEffect;
        VertexDeclaration declaration;
        Bezier bezier;
        VertexPosition2D[] vertices;

        
public  Game1()
        {
            graphics 
=   new  GraphicsDeviceManager( this );
            Content.RootDirectory 
=   " Content " ;

            graphics.PreferredBackBufferWidth 
=   400 ;
            graphics.PreferredBackBufferHeight 
=   300 ;
            graphics.PreferMultiSampling 
=   true ;
            graphics.PreparingDeviceSettings 
+=   new  EventHandler < PreparingDeviceSettingsEventArgs > (graphics_PreparingDeviceSettings);
        }

        
void  graphics_PreparingDeviceSettings( object  sender, PreparingDeviceSettingsEventArgs e)
        {
            
//  Xbox 360 and most PCs support FourSamples/0   
            
//  (4x) and TwoSamples/0 (2x) antialiasing.  
            PresentationParameters pp  =  e.GraphicsDeviceInformation.PresentationParameters;
            
int  quality  =   0 ;
            GraphicsAdapter adapter 
=  e.GraphicsDeviceInformation.Adapter;
            SurfaceFormat format 
=  adapter.CurrentDisplayMode.Format;
            
//  Check for 4xAA  
             if  (adapter.CheckDeviceMultiSampleType(DeviceType.Hardware, format,
                
false , MultiSampleType.FourSamples,  out  quality))
            {
                
//  even if a greater quality is returned, we only want quality 0  
                pp.MultiSampleQuality  =   0 ;
                pp.MultiSampleType 
=
                    MultiSampleType.FourSamples;
            }
            
//  Check for 2xAA  
             else   if  (adapter.CheckDeviceMultiSampleType(DeviceType.Hardware,
                format, 
false , MultiSampleType.TwoSamples,  out  quality))
            {
                
//  even if a greater quality is returned, we only want quality 0  
                pp.MultiSampleQuality  =   0 ;
                pp.MultiSampleType 
=
                    MultiSampleType.TwoSamples;
            }
            
return ;
        } 

        
///   <summary>
        
///      初始化。
        
///   </summary>
         protected   override   void  Initialize()
        {
            declaration 
=   new  VertexDeclaration(GraphicsDevice, VertexPosition2D.VertexElements);

            bezier 
=   new  Bezier( new  Vector2( - 100 - 50 ),  new  Vector2( - 200 150 ),  new  Vector2( 250 200 ),  new  Vector2( 100 - 100 ));
            List
< Vector2 >  positions  =  bezier.GetPoints();
            vertices 
=   new  VertexPosition2D[positions.Count];
            
int  i  =   0 ;
            
foreach  (Vector2 position  in  positions)
            {
                Vector2 pos 
=   new  Vector2(position.X  /  GraphicsDevice.Viewport.Width  *   2 , position.Y  /  GraphicsDevice.Viewport.Height  *   2 );
                vertices[i] 
=   new  VertexPosition2D(pos);
                i
++ ;
            }

            
base .Initialize();
        }

        
///   <summary>
        
///      加载资源。
        
///   </summary>
         protected   override   void  LoadContent()
        {
            lineEffect 
=  Content.Load < Effect > ( " Effects/Line " );
        }

        
///   <summary>
        
///      绘制。
        
///   </summary>
        
///   <param name="gameTime"> Provides a snapshot of timing values. </param>
         protected   override   void  Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            lineEffect.Parameters[
" Color " ].SetValue(Color.White.ToVector4());
            lineEffect.Begin();
            
foreach  (EffectPass pass  in  lineEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                GraphicsDevice.VertexDeclaration 
=  declaration;
                GraphicsDevice.DrawUserPrimitives
< VertexPosition2D > (PrimitiveType.LineStrip, vertices,  0 , vertices.Length  -   1 );
                pass.End();
            }
            lineEffect.End(); 

            
base .Draw(gameTime);
        }
    }
}

   演示效果图如下:

 

你可能感兴趣的:(使用)