平滑曲線生成:貝塞爾曲線擬合


平滑曲線生成是一個很實用的技術

很多時候,我們都需要通過繪制一些折線,然后讓計算機平滑的連接起來,或者是生成一些平滑的面

這里介紹利用一種貝塞爾曲線擬合的方法,先給出我們最終的效果

    

圖1 、折線擬合                                                                      圖2、多邊形擬合(封閉的折線)

 

繼續閱讀本文之前,你需要先掌握貝塞爾曲線的基本知識,這個網上資料很多,這里直接給出源代碼

//count為插入點數, outPoints為輸出點集合,長度為count + 2(含首尾)
void BezierHelper::parseBezier(const Ogre::Vector2& start, const Ogre::Vector2& end,    
    const Ogre::Vector2& control1, const Ogre::Vector2& control2, int count, std::vector<Ogre::Vector2>& outPoints)
{
    if(count < 0) count = Bezier3DefaultPointNum;

    outPoints.push_back(start);
    for(int i = 1; i<=count; i++)
    {
        double st = (double) i/(count+1);
        double dt = (1-st);

        //二次項
        double st2 = st * st; 
        double dt2 = dt * dt;

        double t0 = dt * dt2;
        double t1 = dt2 * st * 3; 
        double t2 = dt * st2 * 3;
        double t3 = st * st2; 

        outPoints.push_back(start * t0 + control1 * t1 + control2 * t2 + end * t3);
    }

    outPoints.push_back(end);
}
View Code

 注:本文實現使用了Ogre的Vector2類,是一個表示二維坐標點的結構,

因為其同時重載了常用了+-*/等算數運算,代碼看起來會比較簡潔,本文最后會附上裁剪的Vector2源文件

 

算法原理:

圖3:當前點pt,當前點前后兩點p1、p2, pt點對應的前后控制點c1、c2

  • 平滑

參考圖3,對於p1-pt-p2兩段線,算法生成p1~pt 和 pt~p2兩條貝塞爾曲線

兩條貝塞爾曲線在pt點對應的控制點分別為c1、c2

因為貝塞爾曲線與該點和其控制點的連線相切,所以保證c1、pt、c2三點共線就可以產生平滑的曲線

  • 控制點自動計算

手工控制各控制點是很繁瑣的,如何自動計算控制點是本算法的核心,算法實現方式為:

  1. 取c1、pt、c2為角p1-pt-p2的角平分線的垂線
  2. 取c1-pt與c2-pt等長,為p1或p2在該垂線上的投影點,(參看圖3,當p1-pt長度大於pt-p2時,取p11點在垂線的投影點作為c1,p11到pt的距離與pt-p2等長)
  3. 對c1、c2做一定比例的縮放,實際取的控制點距pt的距離為投影點距離的0.2-0.6之間時都可以取得很好的平滑效果

實現:

 1 //根據當前點pt,前一點p1, 后一點p2計算當前點對應的控制點control1 control2 
 2 void BezierHelper::getControlPoint(const Ogre::Vector2& pt, const Ogre::Vector2& p1, const Ogre::Vector2& p2, 
 3                                    Ogre::Vector2& control1, Ogre::Vector2& control2, double ratio)
 4 {
 5     double length1 = (p1 - pt).length();
 6     double length2 = (p2 - pt).length();
 7 
 8     double v = length2/(length1+ 0.000001);
 9 
10     Ogre::Vector2 delta;
11     if(v>1)
12     {
13         delta = p1 - (pt + ( p2 - pt) / (v + 0.000001));
14     }
15     else
16     {
17         delta = pt + (p1-pt) * v - p2 ;
18     }
19 
20     delta *= ratio;
21     control1 = pt + delta;
22     control2 = pt - delta;
23 }

ratio參數為調整系數,因為相對與總長,一般取0.1-0.3之間

 

  • 折線生成

同樣方法可以計算p1,p2的前后控制點,這樣通過p1和p1的后控制點、pt和c1可以繪制曲線p1~pt,依次類推....

對於首尾節點,可以簡單的取其控制點為自身

實現:

void BezierHelper::parsePolyline(const std::vector<Ogre::Vector2>& points, int count, std::vector<Ogre::Vector2>& outPoints, double ratio)
{
    std::vector<Ogre::Vector2>::size_type pointsSize = points.size();
    if(pointsSize < 3 )//插值至少需要三點
    {
        for(int i = 0; i<pointsSize; i++) 
        {
            outPoints.push_back(points[i]);
        }
    }
    else
    {
        Ogre::Vector2  control1, control2;    //頂點對應的前后控制點

        getControlPoint(points[1], points[0], points[2], control1, control2, ratio); //首端
        _parseBezier(points[0], points[1], points[0], control1, count, outPoints);

        for(int i = 2; i<pointsSize -1; i++) //根據中間各點生成與前一點連線
        {
            Ogre::Vector2 lastControl = control2;
            getControlPoint(points[i], points[i-1], points[i+1], control1, control2, ratio);
            _parseBezier(points[i - 1], points[i], lastControl, control1, count, outPoints);            
        }

        _parseBezier(points[pointsSize-2], points[pointsSize-1], control2, points[pointsSize-1], count, outPoints);
        outPoints.push_back(points[pointsSize-1]);
    }
}

在運行這段代碼之前,我們需要修改前面的parseBezier函數為_parseBezier,區別僅在於不包括尾節點,方便折線、多邊形生成代碼的調用:

void BezierHelper::_parseBezier(const Ogre::Vector2& start, const Ogre::Vector2& end,    
    const Ogre::Vector2& control1, const Ogre::Vector2& control2, int count, std::vector<Ogre::Vector2>& outPoints)
{
    if(count < 0) count = Bezier3DefaultPointNum;

    outPoints.push_back(start);
    for(int i = 1; i<=count; i++)
    {
        double st = (double) i/(count+1);
        double dt = (1-st);

        //二次項
        double st2 = st * st; 
        double dt2 = dt * dt;

        double t0 = dt * dt2;
        double t1 = dt2 * st * 3; 
        double t2 = dt * st2 * 3;
        double t3 = st * st2; 

        outPoints.push_back(start * t0 + control1 * t1 + control2 * t2 + end * t3);
    }
}
View Code

 

  • 多邊形生成

與折線區別僅在於首點控制點需要通過尾節點生成,尾節點需要利過首節點生成

實現:

void BezierHelper::parsePolygon(const std::vector<Ogre::Vector2>& points, int count, std::vector<Ogre::Vector2>& outPoints, double ratio)
{
    std::vector<Ogre::Vector2>::size_type pointsSize = points.size();
    if(pointsSize < 3 )//插值至少需要三點
    {
        for(int i = 0; i<pointsSize; i++) 
        {
            outPoints.push_back(points[i]);
        }
    }
    else
    {
        Ogre::Vector2  control1, control2;    //頂點對應的前后控制點
        Ogre::Vector2 firstControl;
        Ogre::Vector2 lastControl;

        //首尾
        getControlPoint(points[pointsSize-1], points[pointsSize-2], points[0], firstControl, lastControl, ratio); 
        getControlPoint(points[0], points[pointsSize-1], points[1], control1, control2, ratio); 
        _parseBezier(points[pointsSize-1], points[0], lastControl, control1, count, outPoints);

        for(int i = 1; i<pointsSize-1; i++) //根據中間各點,生成與前一點連線
        {
            lastControl = control2;
            getControlPoint(points[i], points[i-1], points[i+1], control1, control2, ratio);
            _parseBezier(points[i-1], points[i], lastControl, control1, count, outPoints);

        }

        parseBezier(points[pointsSize-2], points[pointsSize-1], control2, firstControl, count, outPoints); //末端
        outPoints.pop_back();
    }
}
View Code

 

附:Ogre::Vector2

#ifndef __Vector2_H__
#define __Vector2_H__
#include <assert.h>

namespace Ogre
{

    class Vector2
    {
    public:
        double x, y;

    public:
        inline Vector2()
        {
        }

        inline Vector2(const double fX, const double fY )
            : x( fX ), y( fY )
        {
        }

        inline Vector2& operator = ( const Vector2& rkVector )
        {
            x = rkVector.x;
            y = rkVector.y;

            return *this;
        }

        inline Vector2& operator = ( const double fScalar)
        {
            x = fScalar;
            y = fScalar;

            return *this;
        }

        inline bool operator == ( const Vector2& rkVector ) const
        {
            return ( x == rkVector.x && y == rkVector.y );
        }

        inline bool operator != ( const Vector2& rkVector ) const
        {
            return ( x != rkVector.x || y != rkVector.y  );
        }

        // arithmetic operations
        inline Vector2 operator + ( const Vector2& rkVector ) const
        {
            return Vector2(
                x + rkVector.x,
                y + rkVector.y);
        }

        inline Vector2 operator - ( const Vector2& rkVector ) const
        {
            return Vector2(
                x - rkVector.x,
                y - rkVector.y);
        }

        inline Vector2 operator * ( const double fScalar ) const
        {
            return Vector2(
                x * fScalar,
                y * fScalar);
        }

        inline Vector2 operator * ( const Vector2& rhs) const
        {
            return Vector2(
                x * rhs.x,
                y * rhs.y);
        }

        inline Vector2 operator / ( const double fScalar ) const
        {
            assert( fScalar != 0.0 );

            double fInv = 1.0f / fScalar;

            return Vector2(
                x * fInv,
                y * fInv);
        }

        inline Vector2 operator / ( const Vector2& rhs) const
        {
            return Vector2(
                x / rhs.x,
                y / rhs.y);
        }

        inline const Vector2& operator + () const
        {
            return *this;
        }

        inline Vector2 operator - () const
        {
            return Vector2(-x, -y);
        }

        // overloaded operators to help Vector2
        inline friend Vector2 operator * ( const double fScalar, const Vector2& rkVector )
        {
            return Vector2(
                fScalar * rkVector.x,
                fScalar * rkVector.y);
        }

        inline friend Vector2 operator / ( const double fScalar, const Vector2& rkVector )
        {
            return Vector2(
                fScalar / rkVector.x,
                fScalar / rkVector.y);
        }

        inline friend Vector2 operator + (const Vector2& lhs, const double rhs)
        {
            return Vector2(
                lhs.x + rhs,
                lhs.y + rhs);
        }

        inline friend Vector2 operator + (const double lhs, const Vector2& rhs)
        {
            return Vector2(
                lhs + rhs.x,
                lhs + rhs.y);
        }

        inline friend Vector2 operator - (const Vector2& lhs, const double rhs)
        {
            return Vector2(
                lhs.x - rhs,
                lhs.y - rhs);
        }

        inline friend Vector2 operator - (const double lhs, const Vector2& rhs)
        {
            return Vector2(
                lhs - rhs.x,
                lhs - rhs.y);
        }

        // arithmetic updates
        inline Vector2& operator += ( const Vector2& rkVector )
        {
            x += rkVector.x;
            y += rkVector.y;

            return *this;
        }

        inline Vector2& operator += ( const double fScaler )
        {
            x += fScaler;
            y += fScaler;

            return *this;
        }

        inline Vector2& operator -= ( const Vector2& rkVector )
        {
            x -= rkVector.x;
            y -= rkVector.y;

            return *this;
        }

        inline Vector2& operator -= ( const double fScaler )
        {
            x -= fScaler;
            y -= fScaler;

            return *this;
        }

        inline Vector2& operator *= ( const double fScalar )
        {
            x *= fScalar;
            y *= fScalar;

            return *this;
        }

        inline Vector2& operator *= ( const Vector2& rkVector )
        {
            x *= rkVector.x;
            y *= rkVector.y;

            return *this;
        }

        inline Vector2& operator /= ( const double fScalar )
        {
            assert( fScalar != 0.0 );

            double fInv = 1.0f / fScalar;

            x *= fInv;
            y *= fInv;

            return *this;
        }

        inline Vector2& operator /= ( const Vector2& rkVector )
        {
            x /= rkVector.x;
            y /= rkVector.y;

            return *this;
        }

        /** Returns the length (magnitude) of the vector.
            @warning
                This operation requires a square root and is expensive in
                terms of CPU operations. If you don't need to know the exact
                length (e.g. for just comparing lengths) use squaredLength()
                instead.
        */
        inline double length () const
        {
            return sqrt( x * x + y * y );
        }

        /** Returns the square of the length(magnitude) of the vector.
            @remarks
                This  method is for efficiency - calculating the actual
                length of a vector requires a square root, which is expensive
                in terms of the operations required. This method returns the
                square of the length of the vector, i.e. the same as the
                length but before the square root is taken. Use this if you
                want to find the longest / shortest vector without incurring
                the square root.
        */
        inline double squaredLength () const
        {
            return x * x + y * y;
        }

        /** Returns the distance to another vector.
            @warning
                This operation requires a square root and is expensive in
                terms of CPU operations. If you don't need to know the exact
                distance (e.g. for just comparing distances) use squaredDistance()
                instead.
        */
        inline double distance(const Vector2& rhs) const
        {
            return (*this - rhs).length();
        }

        /** Returns the square of the distance to another vector.
            @remarks
                This method is for efficiency - calculating the actual
                distance to another vector requires a square root, which is
                expensive in terms of the operations required. This method
                returns the square of the distance to another vector, i.e.
                the same as the distance but before the square root is taken.
                Use this if you want to find the longest / shortest distance
                without incurring the square root.
        */
        inline double squaredDistance(const Vector2& rhs) const
        {
            return (*this - rhs).squaredLength();
        }

        /** Calculates the dot (scalar) product of this vector with another.
            @remarks
                The dot product can be used to calculate the angle between 2
                vectors. If both are unit vectors, the dot product is the
                cosine of the angle; otherwise the dot product must be
                divided by the product of the lengths of both vectors to get
                the cosine of the angle. This result can further be used to
                calculate the distance of a point from a plane.
            @param
                vec Vector with which to calculate the dot product (together
                with this one).
            @return
                A float representing the dot product value.
        */
        inline double dotProduct(const Vector2& vec) const
        {
            return x * vec.x + y * vec.y;
        }

        /** Normalises the vector.
            @remarks
                This method normalises the vector such that it's
                length / magnitude is 1. The result is called a unit vector.
            @note
                This function will not crash for zero-sized vectors, but there
                will be no changes made to their components.
            @return The previous length of the vector.
        */

        inline double normalise()
        {
            double fLength = sqrt( x * x + y * y);

            // Will also work for zero-sized vectors, but will change nothing
            // We're not using epsilons because we don't need to.
            // Read http://www.ogre3d.org/forums/viewtopic.php?f=4&t=61259
            if ( fLength > double(0.0f) )
            {
                double fInvLength = 1.0f / fLength;
                x *= fInvLength;
                y *= fInvLength;
            }

            return fLength;
        }

        /** Returns a vector at a point half way between this and the passed
            in vector.
        */
        inline Vector2 midPoint( const Vector2& vec ) const
        {
            return Vector2(
                ( x + vec.x ) * 0.5f,
                ( y + vec.y ) * 0.5f );
        }

        /** Returns true if the vector's scalar components are all greater
            that the ones of the vector it is compared against.
        */
        inline bool operator < ( const Vector2& rhs ) const
        {
            if( x < rhs.x && y < rhs.y )
                return true;
            return false;
        }

        /** Returns true if the vector's scalar components are all smaller
            that the ones of the vector it is compared against.
        */
        inline bool operator > ( const Vector2& rhs ) const
        {
            if( x > rhs.x && y > rhs.y )
                return true;
            return false;
        }

        /** Sets this vector's components to the minimum of its own and the
            ones of the passed in vector.
            @remarks
                'Minimum' in this case means the combination of the lowest
                value of x, y and z from both vectors. Lowest is taken just
                numerically, not magnitude, so -1 < 0.
        */
        inline void makeFloor( const Vector2& cmp )
        {
            if( cmp.x < x ) x = cmp.x;
            if( cmp.y < y ) y = cmp.y;
        }

        /** Sets this vector's components to the maximum of its own and the
            ones of the passed in vector.
            @remarks
                'Maximum' in this case means the combination of the highest
                value of x, y and z from both vectors. Highest is taken just
                numerically, not magnitude, so 1 > -3.
        */
        inline void makeCeil( const Vector2& cmp )
        {
            if( cmp.x > x ) x = cmp.x;
            if( cmp.y > y ) y = cmp.y;
        }

        /** Generates a vector perpendicular to this vector (eg an 'up' vector).
            @remarks
                This method will return a vector which is perpendicular to this
                vector. There are an infinite number of possibilities but this
                method will guarantee to generate one of them. If you need more
                control you should use the Quaternion class.
        */
        inline Vector2 perpendicular(void) const
        {
            return Vector2 (-y, x);
        }

        /** Calculates the 2 dimensional cross-product of 2 vectors, which results
            in a single floating point value which is 2 times the area of the triangle.
        */
        inline double crossProduct( const Vector2& rkVector ) const
        {
            return x * rkVector.y - y * rkVector.x;
        }

        /** Returns true if this vector is zero length. */
        inline bool isZeroLength(void) const
        {
            double sqlen = (x * x) + (y * y);
            return (sqlen < (1e-06 * 1e-06));

        }

        /** As normalise, except that this vector is unaffected and the
            normalised vector is returned as a copy. */
        inline Vector2 normalisedCopy(void) const
        {
            Vector2 ret = *this;
            ret.normalise();
            return ret;
        }

        /** Calculates a reflection vector to the plane with the given normal .
        @remarks NB assumes 'this' is pointing AWAY FROM the plane, invert if it is not.
        */
        inline Vector2 reflect(const Vector2& normal) const
        {
            return Vector2( *this - ( 2 * this->dotProduct(normal) * normal ) );
        }

 
    };

}
#endif
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM