1. 程式人生 > >採用Cardinal法構造插枝分段三次樣條曲線 : 程式碼篇

採用Cardinal法構造插枝分段三次樣條曲線 : 程式碼篇

說明:Spline類就是Cardinal樣條曲線了,這個類裡面記錄了4個控制點:m_startControlPoint, m_startPoint, m_endPoint, m_endControlPoint, 分別按順序對應Pk-1, Pk, Pk+1, Pk+2, 由於Cardinal是用多個線段去模擬曲線,所以我們在這裡取20個點,這樣,在Pk 和 Pk+1之間,有20個樣本點,用畫直線的方法將這些點按順序連起來,連成的曲線段就是我們所要的Cardinal 曲線了

本文由timewolf完成,首發於CSDN,作者保留版權。
未經許可,不得使用於任何商業用途。
如需聯絡請發郵件:karla9(AT)eyou(dot)com

using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace CardinalTest
{
 /// <summary>
 /// the declaration of the Spline, each spline contains 4 control points
 /// </summary>
 public class Spline
 {
  // it's the "Pk-1" point
  private Point3D m_startControlPoint;
  // it's the "Pk" point
  private Point3D m_startPoint;
  // it's the "Pk+1" point
  private Point3D m_endPoint;
  // it's the "Pk+2" point
  private Point3D m_endControlPoint;
  // that means we'll generate 20 sample points between Pk & Pk+1, so "u" will start from 0.00F
  // and the increase step is 0.05F
  private static readonly int m_sampleCount = 20;
  // the "t" in Cardinal altorithm
  private static readonly float m_tension = 0.0F;
  // store the result sample points
  private Point3D[] m_ctrlPoints;
  // indicate whether this spline is the first, if it is,
  //the m_startControlPoint & m_startPoint will be the same
  // because the 4 points can just determine the spline between Pk & Pk+1, so we add a point ahead
  // of the Pk-1 manually, so that we can draw the spline between Pk-1 & Pk+1
  // similarly, the last Spline's "Pk+2" will be the same as its "Pk+1"
  // so that we can draw the spline between Pk+1 & Pk+2
  private bool m_isFirst = false;

  #region Associated Property Block
  public Point3D StartControlPoint
  {
   get
   {
    return this.m_startControlPoint;
   }
   set
   {
    this.m_startControlPoint = value;
   }
  }

  public Point3D StartPoint
  {
   get
   {
    return this.m_startPoint;
   }
   set
   {
    this.m_startPoint = value;
   }
  }

  public Point3D EndPoint
  {
   get
   {
    return this.m_endPoint;
   }
   set
   {
    this.m_endPoint = value;
   }
  }

  public Point3D EndControlPoint
  {
   get
   {
    return this.m_endControlPoint;
   }
   set
   {
    this.m_endControlPoint = value;
   }
  }

  public Point3D[] CtrlPoints
  {
   get
   {
    return this.m_ctrlPoints;
   }
  }

  public bool IsFirst
  {
   get
   {
    return this.m_isFirst;
   }
   set
   {
    this.m_isFirst = value;
   }
  }
  #endregion

  public Spline()
  {
   m_startControlPoint = new Point3D();
   m_startPoint = new Point3D();
   m_endPoint = new Point3D();
   m_endControlPoint = new Point3D();
   m_ctrlPoints = new Point3D[m_sampleCount + 1];
   for(int i = 0; i < m_ctrlPoints.Length; i++)
   {
    m_ctrlPoints[i] = new Point3D();
   }
  }

  /// <summary>
  /// A new control point added to the control points, so the previous splines should be updated
  /// </summary>
  public void AddJoint(Spline prevSpline, Point3D currentPoint)
  {
   // the previous spline is null, so there's just one point in the control point list;
   // so the 4 control points are all the same
   // while the 2nd & later points added into the list, the 1st spline's Pk+1 & Pk+2
   // will be updated
   if (null == prevSpline)
   {
    this.m_startControlPoint = currentPoint;
    this.m_startPoint = currentPoint;
    this.m_endPoint = currentPoint;
    this.m_endControlPoint = currentPoint;
    this.m_isFirst = true;
   }
   // the previous spline is not null, so update the previous spline's control points
   // and update this Spline's points
   else
   {
    // the previous spline is the 1st spline, update its Pk+1 & Pk+2
    if (true == prevSpline.m_isFirst)
    {
     this.m_startControlPoint = prevSpline.StartControlPoint;
     this.m_startPoint = prevSpline.StartPoint;
     this.m_endPoint = currentPoint;
     this.m_endControlPoint = currentPoint;
     GenerateSamplePoint();
     return;
    }
    // the previous spline is not the 1st, just update its Pk+2
    else
    {
     prevSpline.EndControlPoint = currentPoint;
     prevSpline.GenerateSamplePoint();
    
     // simulate the sample points of current Spline
     this.m_startControlPoint = prevSpline.m_startPoint;
     this.m_startPoint = prevSpline.m_endPoint;
     this.m_endPoint = currentPoint;
     this.m_endControlPoint = currentPoint;
     GenerateSamplePoint();

    }   
   }
  }

  /// <summary>
  /// use Cardinal altorithm to generate the sample points
  /// </summary>
  public void GenerateSamplePoint()
  {
   Point3D startControlPoint = this.StartControlPoint;
   Point3D startPoint = this.StartPoint;
   Point3D endPoint = this.EndPoint;
   Point3D endControlPoint = this.EndControlPoint;
   float step = 1.0F / (float)m_sampleCount;
   float uValue = 0.00F;

   for (int i = 0; i < m_sampleCount; i++)
   {
    Point3D pointNew = Cardinal(uValue, startControlPoint, startPoint, endPoint, endControlPoint);
    this.CtrlPoints[i] = new Point3D(pointNew);
    uValue += step;
   }
   this.CtrlPoints[m_ctrlPoints.Length - 1] = endPoint;
  }

  public void Draw()
  {
   for (int i = 0; i < m_ctrlPoints.Length - 1; i++ )
   {
    Point3D lastP3D = m_ctrlPoints[i];
    Point3D nextP3D = m_ctrlPoints[i + 1];
    DrawLine(lastP3D, nextP3D, dexStart + i);
   }
  }


  #region Cardinal algorithm
  /// <summary>
  /// Get a point on the result curve, this point is between startPoint & endPoint
  /// </summary>
  /// <param name="u">
  /// the variable between 0 & 1, the input parameter
  /// </param>
  /// <param name="startControlPoint">
  /// the control point in front of start point, help to determine the appearance of the result curve
  /// </param>
  /// <param name="startPoint">
  /// the start point of the result curve, when u == 0, the returned result is startPoint
  /// </param>
  /// <param name="endPoint">
  /// the end point of the result curve, when u == 1, the returned result is endPoint
  /// </param>
  /// <param name="endControlPoint">
  /// the control point after start point, help to determine the appearance of the result curve
  /// </param>
  /// <returns>
  /// the result point between startPoint & endPoint
  /// </returns>
  private Point3D Cardinal(float u, Point3D startControlPoint,
   Point3D startPoint, Point3D endPoint, Point3D endControlPoint)
  {
   float s = (1 - m_tension) / 2;
   Point3D resultPoint = new Point3D();
   resultPoint.X = GetResult(startControlPoint.X, startPoint.X, endPoint.X, endControlPoint.X, s, u);
   resultPoint.Y = GetResult(startControlPoint.Y, startPoint.Y, endPoint.Y, endControlPoint.Y, s, u);
   resultPoint.Z = GetResult(startControlPoint.Z, startPoint.Z, endPoint.Z, endControlPoint.Z, s, u);
   return resultPoint;
  }

  private float GetResult(float a, float b, float c, float d, float s, float u)
  {
   float result = 0.0F;
   result = a*(2*s*u*u - s*u*u*u - s*u) + b*((2-s)*u*u*u + (s-3)*u*u + 1) +
    c*((s-2)*u*u*u + (3-2*s)*u*u + s*u) + d*(s*u*u*u - s*u*u);
   return result;
  }
  #endregion
 }
}

namespace CardinalTest
{
 /// <summary>
 /// Summary description for Point3D.
 /// </summary>
 public class Point3D
 {
  private float m_x;
  private float m_y;
  private float m_z;
  public float sizeModulus;

  #region Associated Property Declaration
  public float X
  {
   get
   {
    return m_x;
   }
   set
   {
    m_x = value;
   }
  }

  public float Y
  {
   get
   {
    return m_y;
   }
   set
   {
    m_y = value;
   }
  }

  public float Z
  {
   get
   {
    return m_z;
   }
   set
   {
    m_z = value;
   }
  }
  #endregion


  public Point3D()
  {
   m_x = 0.0F;
   m_y = 0.0F;
   m_z = 0.0F;
  }

  public Point3D(Point3D sourcePoint)
  {
   m_x = sourcePoint.X;
   m_y = sourcePoint.Y;
   m_z = sourcePoint.Z;
  }


  public Point3D(float x, float y, float z)
  {
   m_x = x;
   m_y = y;
   m_z = z;
  }


  public Point3D(double x, double y, double z)
  {
   m_x = (float)x;
   m_y = (float)y;
   m_z = (float)z;
  }

 }
}

使用例項:

定義一個arraylist: m_currentSplines,用來記錄所有的spline

在mousedown裡面呼叫addJoint函式,如果畫完了,就呼叫draw函式~~

  /// <summary>
  /// Add joint to the path
  /// </summary>
  /// <param name="nowP3D"></param>
  public void AddJoint(Point3D nowP3D)
  {
   if (null == m_currentSplines || 0 == m_currentSplines.Count)
   {
    Spline splineNew = new Spline();
    splineNew.AddJoint(null, nowP3D);
    m_currentSplines.Add(splineNew);
   }
   else
   {
    Spline splineNew = new Spline();
    Spline lastNew = m_currentSplines[m_currentSplines.Count - 1] as Spline;
    splineNew.AddJoint(lastNew, nowP3D);
    m_currentSplines.Add(splineNew);
   }
  }

  /// <summary>
  /// Redraw the path and joints
  /// </summary>
  /// <returns></returns>
  public void Draw()
  {

    int count = m_currentSplines.Count;
    for (int i = 0; i < count; i++)
    {
     Spline spline = m_currentSplines[i] as Spline;
     if (true == spline.IsFirst)
     {
      continue;
     }
     spline.Draw();
    }
  }