双圆弧插值算法(三,代码实现)
双圆弧插值算法(三,代码实现)
交互式演示
这是一个用HTML5编写的交互式演示。要移动控制点,请单击并拖动它们。若要移动切线,请单击并拖动控制点外的区域。默认情况下,曲线保持d1和d2相等,但也可以在下面指定自定义d1值。

 
代码
到目前为止,我们只讨论了二维情况。让我们编写一些C++代码来解决三维的情况。它非常相似,除了每个弧可以在不同的平面上对齐。这将在查找旋转方向和平面法线时创建一些调整。经过几次交叉积后,一切都成功了。
这些代码示例是在以下许可下发布的。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /******************************************************************************  Copyright (c) 2014 Ryan Juckett   This software is provided 'as-is', without any express or implied  warranty. In no event will the authors be held liable for any damages  arising from the use of this software.   Permission is granted to anyone to use this software for any purpose,  including commercial applications, and to alter it and redistribute it  freely, subject to the following restrictions:   1. The origin of this software must not be misrepresented; you must not     claim that you wrote the original software. If you use this software     in a product, an acknowledgment in the product documentation would be     appreciated but is not required.   2. Altered source versions must be plainly marked as such, and must not be     misrepresented as being the original software.   3. This notice may not be removed or altered from any source     distribution.******************************************************************************/ | 
Here's our vector type. It's about as basic as vector types come.
| 1 2 3 4 5 6 7 8 | //******************************************************************************//******************************************************************************structtVec3{    floatm_x;    floatm_y;    floatm_z;}; | 
Now, let's define some math functions to help with common operations (mostly linear algebra).
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | //******************************************************************************// Compute the dot product of two vectors.//******************************************************************************floatVec_DotProduct(consttVec3 & lhs, consttVec3 & rhs){    returnlhs.m_x*rhs.m_x + lhs.m_y*rhs.m_y + lhs.m_z*rhs.m_z;}//******************************************************************************// Compute the cross product of two vectors.//******************************************************************************voidVec_CrossProduct(tVec3 * pResult, consttVec3 & lhs, consttVec3 & rhs){    floatx = lhs.m_y*rhs.m_z - lhs.m_z*rhs.m_y;    floaty = lhs.m_z*rhs.m_x - lhs.m_x*rhs.m_z;    floatz = lhs.m_x*rhs.m_y - lhs.m_y*rhs.m_x;    pResult->m_x = x;    pResult->m_y = y;    pResult->m_z = z;}//******************************************************************************// Compute the sum of two vectors.//******************************************************************************voidVec_Add(tVec3 * pResult, consttVec3 & lhs, consttVec3 & rhs){    pResult->m_x = lhs.m_x + rhs.m_x;    pResult->m_y = lhs.m_y + rhs.m_y;    pResult->m_z = lhs.m_z + rhs.m_z;}//******************************************************************************// Compute the difference of two vectors.//******************************************************************************voidVec_Subtract(tVec3 * pResult, consttVec3 & lhs, consttVec3 & rhs){    pResult->m_x = lhs.m_x - rhs.m_x;    pResult->m_y = lhs.m_y - rhs.m_y;    pResult->m_z = lhs.m_z - rhs.m_z;}//******************************************************************************// Compute a scaled vector.//******************************************************************************voidVec_Scale(tVec3 * pResult, consttVec3 & lhs, floatrhs){    pResult->m_x = lhs.m_x*rhs;    pResult->m_y = lhs.m_y*rhs;    pResult->m_z = lhs.m_z*rhs;}//******************************************************************************// Add a vector to a scaled vector.//******************************************************************************voidVec_AddScaled(tVec3 * pResult, consttVec3 & lhs, consttVec3 & rhs, floatrhsScale){    pResult->m_x = lhs.m_x + rhs.m_x*rhsScale;    pResult->m_y = lhs.m_y + rhs.m_y*rhsScale;    pResult->m_z = lhs.m_z + rhs.m_z*rhsScale;}//******************************************************************************// Compute the magnitude of a vector.//******************************************************************************floatVec_Magnitude(consttVec3 & lhs){    returnsqrtf(Vec_DotProduct(lhs,lhs));}//******************************************************************************// Check if the vector length is within epsilon of 1//******************************************************************************boolVec_IsNormalized_Eps(consttVec3 & value, floatepsilon){    constfloatsqrMag = Vec_DotProduct(value,value);    returnsqrMag >= (1.0f - epsilon)*(1.0f - epsilon)            &&  sqrMag <= (1.0f + epsilon)*(1.0f + epsilon);}//******************************************************************************// Return 1 or -1 based on the sign of a real number.//******************************************************************************inlinefloatSign(floatval){    return(val < 0.0f) ? -1.0f : 1.0f;} | 
The algorithm is broken up into two parts. First, we provide a function to compute the pair of arcs making up the curve (this basically covers all of the fun math I showed above). Second, we provide a function to interpolate across the biarc curve. This is the helper type representing a computed arc. It is the output of the first part (biarc computation) and the input to the second part (biarc interpolation).
The set of data stored in this type has been chosen to reduce the number of operations in the interpolation process. This lets the user compute the biarc once, and then compute a bunch of points along it very fast. In our interpolation function, we will do a proper blend across each circular arc, but you might instead want to convert the biarc into something like an approximate Bézier curve such that it is compatible with other systems. In that case, you might not need all these values.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //******************************************************************************// Information about an arc used in biarc interpolation. Use // Vec_BiarcInterp_ComputeArcs to compute the values and use Vec_BiarcInterp// to interpolate along the arc pair.//******************************************************************************structtBiarcInterp_Arc{    tVec3   m_center;   // center of the circle (or line)    tVec3   m_axis1;    // vector from center to the end point    tVec3   m_axis2;    // vector from center edge perpendicular to axis1    floatm_radius;   // radius of the circle (zero for lines)    floatm_angle;    // angle to rotate from axis1 towards axis2    floatm_arcLen;   // distance along the arc}; | 
This is a helper function for computing data about a single arc. This isn't intended as a user facing function.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | //******************************************************************************// Compute a single arc based on an end point and the vector from the endpoint// to connection point. //******************************************************************************voidBiarcInterp_ComputeArc(    tVec3 *         pCenter,    // Out: Center of the circle or straight line.    float*         pRadius,    // Out: Zero for straight lines    float*         pAngle,     // Out: Angle of the arc    consttVec3 &   point,    consttVec3 &   tangent,    consttVec3 &   pointToMid){    // assume that the tangent is normalized    assert( Vec_IsNormalized_Eps(tangent,0.01f) );    constfloatc_Epsilon = 0.0001f;    // compute the normal to the arc plane    tVec3 normal;    Vec_CrossProduct(&normal, pointToMid, tangent);    // Compute an axis within the arc plane that is perpendicular to the tangent.    // This will be coliniear with the vector from the center to the end point.    tVec3 perpAxis;    Vec_CrossProduct(&perpAxis, tangent, normal);    constfloatdenominator = 2.0f * Vec_DotProduct(perpAxis, pointToMid);                if(fabs(denominator) < c_Epsilon)    {        // The radius is infinite, so use a straight line. Place the center point in the        // middle of the line.        Vec_AddScaled(pCenter, point, pointToMid, 0.5f);        *pRadius = 0.0f;        *pAngle  = 0.0f;    }    else    {        // Compute the distance to the center along perpAxis        constfloatcenterDist = Vec_DotProduct(pointToMid,pointToMid) / denominator;        Vec_AddScaled(pCenter, point, perpAxis, centerDist);        // Compute the radius in absolute units        constfloatperpAxisMag = Vec_Magnitude(perpAxis);        constfloatradius = fabs(centerDist*perpAxisMag);        // Compute the arc angle        floatangle;        if(radius < c_Epsilon)        {            angle = 0.0f;        }        else        {            constfloatinvRadius = 1.0f / radius;                                // Compute normalized directions from the center to the connection point            // and from the center to the end point.            tVec3 centerToMidDir;            tVec3 centerToEndDir;                                Vec_Subtract(¢erToMidDir, point, *pCenter);            Vec_Scale(¢erToEndDir, centerToMidDir, invRadius);            Vec_Add(¢erToMidDir, centerToMidDir, pointToMid);            Vec_Scale(¢erToMidDir, centerToMidDir, invRadius);            // Compute the rotation direction            constfloattwist = Vec_DotProduct(perpAxis, pointToMid);            // Compute angle.            angle = acosf( Vec_DotProduct(centerToEndDir,centerToMidDir) ) * Sign(twist);        }        // output the radius and angle        *pRadius = radius;        *pAngle  = angle;    }} | 
Here is the user facing function to generate a biarc from a pair of control points. It only supports the case where d1d1 and d2d2 are solved to be equal.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | //******************************************************************************// Compute a pair of arcs to pass into Vec_BiarcInterp//******************************************************************************voidBiarcInterp_ComputeArcs(    tBiarcInterp_Arc *  pArc1,    tBiarcInterp_Arc *  pArc2,    consttVec3 &   p1,     // start position    consttVec3 &   t1,     // start tangent    consttVec3 &   p2,     // end position    consttVec3 &   t2      // end tangent){    assert( Vec_IsNormalized_Eps(t1,0.01f) );    assert( Vec_IsNormalized_Eps(t2,0.01f) );    constfloatc_Pi        = 3.1415926535897932384626433832795f;    constfloatc_2Pi       = 6.2831853071795864769252867665590f;    constfloatc_Epsilon   = 0.0001f;    tVec3 v;    Vec_Subtract(&v, p2, p1);    constfloatvDotV = Vec_DotProduct(v,v);    // if the control points are equal, we don't need to interpolate    if(vDotV < c_Epsilon)    {        pArc1->m_center = p1;        pArc2->m_radius = 0.0f;        pArc1->m_axis1 = v;        pArc1->m_axis2 = v;        pArc1->m_angle = 0.0f;        pArc1->m_arcLen = 0.0f;        pArc2->m_center = p1;        pArc2->m_radius = 0.0f;        pArc2->m_axis1 = v;        pArc2->m_axis2 = v;        pArc2->m_angle = 0.0f;        pArc2->m_arcLen = 0.0f;        return;    }    // computw the denominator for the quadratic formula    tVec3 t;    Vec_Add(&t, t1, t2);    constfloatvDotT       = Vec_DotProduct(v,t);    constfloatt1DotT2     = Vec_DotProduct(t1,t2);    constfloatdenominator = 2.0f*(1.0f - t1DotT2);    // if the quadratic formula denominator is zero, the tangents are equal and we need a special case    floatd;    if(denominator < c_Epsilon)    {        constfloatvDotT2 = Vec_DotProduct(v,t2);                        // if the special case d is infinity, the only solution is to interpolate across two semicircles        if( fabs(vDotT2) < c_Epsilon )        {            constfloatvMag = sqrtf(vDotV);            constfloatinvVMagSqr = 1.0f / vDotV;            // compute the normal to the plane containing the arcs            // (this has length vMag)            tVec3 planeNormal;            Vec_CrossProduct(&planeNormal, v, t2);            // compute the axis perpendicular to the tangent direction and aligned with the circles            // (this has length vMag*vMag)            tVec3 perpAxis;            Vec_CrossProduct(&perpAxis, planeNormal, v);            floatradius= vMag * 0.25f;            tVec3 centerToP1;            Vec_Scale(¢erToP1, v, -0.25f);                                    // interpolate across two semicircles            Vec_Subtract(&pArc1->m_center, p1, centerToP1);            pArc1->m_radius= radius;            pArc1->m_axis1= centerToP1;            Vec_Scale(&pArc1->m_axis2, perpAxis, radius*invVMagSqr);            pArc1->m_angle= c_Pi;            pArc1->m_arcLen= c_Pi * radius;                                Vec_Add(&pArc2->m_center, p2, centerToP1);            pArc2->m_radius= radius;            Vec_Scale(&pArc2->m_axis1, centerToP1, -1.0f);            Vec_Scale(&pArc2->m_axis2, perpAxis, -radius*invVMagSqr);            pArc2->m_angle= c_Pi;            pArc2->m_arcLen= c_Pi * radius;            return;        }        else        {            // compute distance value for equal tangents            d = vDotV / (4.0f * vDotT2);        }               }    else    {        // use the positive result of the quadratic formula        constfloatdiscriminant = vDotT*vDotT + denominator*vDotV;        d = (-vDotT + sqrtf(discriminant)) / denominator;    }    // compute the connection point (i.e. the mid point)    tVec3 pm;    Vec_Subtract(&pm, t1, t2);    Vec_AddScaled(&pm, p2, pm, d);    Vec_Add(&pm, pm, p1);    Vec_Scale(&pm, pm, 0.5f);    // compute vectors from the end points to the mid point    tVec3 p1ToPm, p2ToPm;    Vec_Subtract(&p1ToPm, pm, p1);    Vec_Subtract(&p2ToPm, pm, p2);                                // compute the arcs    tVec3 center1, center2;    floatradius1, radius2;    floatangle1, angle2;    BiarcInterp_ComputeArc( ¢er1, &radius1, &angle1, p1, t1, p1ToPm );    BiarcInterp_ComputeArc( ¢er2, &radius2, &angle2, p2, t2, p2ToPm );                // use the longer path around the circle if d is negative    if(d < 0.0f)    {        angle1= Sign(angle1)*c_2Pi - angle1;        angle2= Sign(angle2)*c_2Pi - angle2;    }    // output the arcs    // (the radius will be set to zero when the arc is a straight line)    pArc1->m_center = center1;    pArc1->m_radius = radius1;    Vec_Subtract(&pArc1->m_axis1, p1, center1); // redundant from Vec_BiarcInterp_ComputeArc    Vec_Scale(&pArc1->m_axis2, t1, radius1);    pArc1->m_angle = angle1;    pArc1->m_arcLen = (radius1 == 0.0f) ? Vec_Magnitude(p1ToPm) : fabs(radius1 * angle1);    pArc2->m_center = center2;    pArc2->m_radius = radius2;    Vec_Subtract(&pArc2->m_axis1, p2, center2); // redundant from Vec_BiarcInterp_ComputeArc    Vec_Scale(&pArc2->m_axis2, t2, -radius2);    pArc2->m_angle = angle2;    pArc2->m_arcLen = (radius2 == 0.0f) ? Vec_Magnitude(p2ToPm) : fabs(radius2 * angle2);} | 
Finally, we have the function to compute the fractional position along the biarc curve. Arc length is used to create a smooth distribution.
Before interpolating, it is sometimes useful to inspect the computed arcs. For example, you might decide that a biarc with too much curvature will look bad and switch to linear interpolation. This could be checked by seeing if the arc lengths sum up to be greater than a semicircle placed at the control points (i.e. arcLen1+arcLen2>π2∣p2−p1∣arcLen1+arcLen2>π2∣p2−p1∣).
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | //******************************************************************************// Use a biarc to interpolate between two points such that the interpolation// direction aligns with associated tangents.//******************************************************************************voidBiarcInterp(    tVec3 *                     pResult,    // interpolated point    consttBiarcInterp_Arc &    arc1,    consttBiarcInterp_Arc &    arc2,    floatfrac        // [0,1] fraction along the biarc){    assert( frac >= 0.0f && frac <= 1.0f );                            constfloatepsilon = 0.0001f;                            // compute distance along biarc    constfloattotalDist = arc1.m_arcLen + arc2.m_arcLen;    constfloatfracDist = frac * totalDist;                        // choose the arc to evaluate    if(fracDist < arc1.m_arcLen)    {        if(arc1.m_arcLen < epsilon)        {            // just output the end point            Vec_Add(pResult, arc1.m_center, arc1.m_axis1);        }        else        {            constfloatarcFrac = fracDist / arc1.m_arcLen;            if(arc1.m_radius == 0.0f)            {                // interpolate along the line                Vec_AddScaled(pResult, arc1.m_center, arc1.m_axis1, -arcFrac*2.0f + 1.0f);            }            else            {                // interpolate along the arc                floatangle = arc1.m_angle*arcFrac;                floatsinRot = sinf(angle);                floatcosRot = cosf(angle);                Vec_AddScaled(pResult, arc1.m_center, arc1.m_axis1, cosRot);                Vec_AddScaled(pResult, *pResult, arc1.m_axis2, sinRot);            }        }    }    else    {        if(arc2.m_arcLen < epsilon)        {            // just output the end point            Vec_Add(pResult, arc1.m_center, arc1.m_axis2);        }        else        {            constfloatarcFrac = (fracDist-arc1.m_arcLen) / arc2.m_arcLen;            if(arc2.m_radius == 0.0f)            {                // interpolate along the line                Vec_AddScaled(pResult, arc2.m_center, arc2.m_axis1, arcFrac*2.0f - 1.0f);            }            else            {                // interpolate along the arc                floatangle = arc2.m_angle*(1.0f-arcFrac);                floatsinRot = sinf(angle);                floatcosRot = cosf(angle);                Vec_AddScaled(pResult, arc2.m_center, arc2.m_axis1, cosRot);                Vec_AddScaled(pResult, *pResult, arc2.m_axis2, sinRot);            }        }    }} | 
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号