Hermite (埃尔米特)曲线

Hermite 曲线

  已知曲线的两个端点坐标P0、P1,和端点处的切线R0、R1,确定的一条曲线。

参数方程

  1. 几何形式

  

  2. 矩阵形式

   

  3. 推导

    

  

  

  

  

  

例子分析

  

  如上图有四个点,假如P0、P2是端点,那么向量R0=(P1-P0),R1=(P3-P2),将数据带入调和函数,即求得曲线。

  在程序中,我们通常会使用特殊方法处理顶点之间的关系。

  

  图中含有3个顶点,我们把每挨着的两个顶点看做是一条Hermite曲线,P0和P1是两个端点,那么现在,我们如何求得R1呢? 我们现在构建连个参考点F1,F2。

    令  F1 = P0;   F2 = P2;

    那么 R1 = P1-F1;  R2 = F2-P1;

  然后将此值带入曲线函数,即可为求得的曲线。  

程序代码

该代码是Unity脚本代码:

  1. 实现编辑器闭合曲线和非闭合曲线的绘制;

  2. 运行脚本,可以实现物体跟随曲线路径移动,可以勾选旋转跟随与不跟随;

  3. 如果不进行自动跟随曲线路径,可以修改时间值,移动物体。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class HermitCurve : Curve_Root {

    public Transform CurveParent;
    public int lineCount;
    private List<NodePath> nodePath;
    public float smoothFactor = 2.0f;

    private Transform[] transforms;
    private Vector3[] node;
    public float Speed = 10.0f;

    Vector3 curPos;
    Vector3 nextPos;
    Quaternion curRotate;
    Quaternion nextRotate;
    float moveTime;
    float curTime;
    public GameObject Tar;
    int index;
    private Transform T;

    public float time;

    /// <summary>
    /// 数据校正
    /// </summary>
    void DadaCorrection()
    {
        //根节点验证
        if (CurveParent == null)
        {
            Debug.LogError("Please add curve the root node.");
            return;
        }

        //修正平滑因子: 2时,较为理想。
        smoothFactor = smoothFactor >= 10.0f ? 10.0f : smoothFactor;
        smoothFactor = smoothFactor <= 1.0f ? 1.0f : smoothFactor;
    }

    /// <summary>
    /// 计算路径节点
    /// </summary>
    void ComputeNode()
    {
        //将节点添加入链表
        Component[] mTrans = CurveParent.GetComponentsInChildren(typeof(Transform));//物体和子物体的Trans
        if (mTrans.Length - 1 < 2)
        {
            Debug.LogError("Please add at least two points.");
            return;
        }

        //非闭合曲线与闭合曲线的顶点时间计算:非闭合曲线,最后个顶点的时间为1.0,闭合曲线的最后个顶点的时间为倒数第二个顶点,因为他的最后个点是原点。
        float t;
        if (closedCurve)
        {
            t = 1.0f / (mTrans.Length - 1);
            nodePath = new List<NodePath>();
            for (int i = 1; i < mTrans.Length; i++)//根节点不参与路径节点的计算
            {
                nodePath.Add(new NodePath(mTrans[i].transform.position, (i - 1) * t));
            }
            //闭合曲线完整的节点
            AddClosedCurveNode();
        }
        else
        {
            t = 1.0f / (mTrans.Length - 2);
            nodePath = new List<NodePath>();
            for (int i = 1; i < mTrans.Length; i++)//根节点不参与路径节点的计算
            {
                nodePath.Add(new NodePath(mTrans[i].transform.position, (i - 1) * t));
            }
            //非闭合曲线完整的节点
            AddCurveNode();
        }
    }

    // Use this for initialization
    void Start () {

        DadaCorrection();

        ComputeNode();

        node = new Vector3[lineCount+1];
        //Vector3 start = nodePath[1].point;
        //Vector3 end;

        node[0] = nodePath[1].point;
        Vector3 end;
        //绘制节点
        for (int i = 1; i <= lineCount; i++)
        {
            float ti = i / (float)lineCount;
            end = GetHermitAtTime(ti);
            if (node != null)
            {
                node[i] = end;
            }
        }

        T = new GameObject().transform;

        curPos = node[0];
        nextPos = node[1];
        moveTime = (nextPos - curPos).magnitude / Speed;

        Tar.transform.position = curPos;
        Tar.transform.transform.LookAt(nextPos);
        curRotate = Tar.transform.rotation;
        T.position = node[1];
        T.LookAt(node[2]);
        nextRotate = T.rotation;
        curTime = 0;
        index = 1;
    }

    // Update is called once per frame
    void Update () {

        if (AutoCurve)
        {
            if (moveTime > curTime)
            {
                Tar.transform.position = Vector3.Lerp(curPos, nextPos, curTime / moveTime);
                if (AutoRotate)
                {
                    Tar.transform.rotation = Quaternion.Slerp(curRotate, nextRotate, curTime / moveTime);
                }

                curTime += Time.deltaTime;
            }
            else
            {
                if (closedCurve)
                {
                    index = ((index + 1) % (lineCount + 1) == 0) ? 0 : index + 1;
                }
                else
                {
                    index = ((index + 1) % (lineCount+1) == 0) ? index : index + 1;
                }
                curPos = nextPos;
                nextPos = node[index];

                curTime = 0;
                moveTime = (nextPos - curPos).magnitude / Speed;

                T.position = node[index];
                curRotate = nextRotate;

                if (closedCurve)
                {
                    int c1;
                    if (index == node.Length-1)
                    {
                        c1 = 0;
                    }
                    else
                    {
                        c1 = index + 1;
                    }
                    T.LookAt(node[c1]);
                }
                else
                {
                    int c1;
                    if (index == node.Length - 1)
                    {
                        c1 = 0;
                    }
                    else
                    {
                        c1 = index + 1;
                        T.LookAt(node[c1]);
                    }
                }

                nextRotate = T.rotation;
            }
        }
        else
        {
            if (closedCurve)
            {
                if (AutoRotate)
                {
                    time = time > 1.0f ? 0.0f : time;
                    time = time < 0.0f ? 1.0f : time;
                    Vector3 cur = GetHermitAtTime(time);
                    Tar.transform.position = cur;

                    float del = 1.0f / lineCount;
                    Vector3 next0 = GetHermitAtTime(time + del);
                    Tar.transform.LookAt(next0);
                }
                else
                {
                    time = time > 1.0f ? 0.0f : time;
                    time = time < 0.0f ? 1.0f : time;
                    Vector3 cur = GetHermitAtTime(time);
                    Tar.transform.position = cur;
                }
            }
            else
            {
                if (AutoRotate)
                {
                    time = time > 1.0f ? 1.0f : time;
                    time = time < 0.0f ? 0.0f : time;
                    Vector3 cur = GetHermitAtTime(time);
                    Tar.transform.position = cur;

                    float del = 1.0f / lineCount;
                    Vector3 next0 = GetHermitAtTime(time + del);

                    Tar.transform.LookAt(next0);
                }
                else
                {
                    time = time > 1.0f ? 1.0f : time;
                    time = time < 0.0f ? 0.0f : time;
                    Vector3 cur = GetHermitAtTime(time);
                    Tar.transform.position = cur;
                }
            }

        }

    }

    /// <summary>
    /// 绘制曲线
    /// </summary>
    void DrawCurve()
    {
        if (closedCurve)
        {
            Vector3 start = nodePath[1].point;
            Vector3 end;

            Gizmos.color = _Color;
            //绘制节点
            for (int i = 1; i < lineCount; i++)
            {
                float time = i / (float)lineCount;
                end = GetHermitAtTime(time);
                //Debug.Log(end);
                Gizmos.DrawLine(start, end);

                start = end;
            }
            Gizmos.DrawLine(start, nodePath[nodePath.Count - 2].point);
        }
        else
        {
            Vector3 start = nodePath[1].point;
            Vector3 end;

            Gizmos.color = _Color;
            //绘制节点
            for (int i = 1; i < lineCount; i++)
            {
                float time = i / (float)lineCount;
                end = GetHermitAtTime(time);
                //Debug.Log(end);
                Gizmos.DrawLine(start, end);
                start = end;
            }
            Gizmos.DrawLine(start, nodePath[nodePath.Count - 2].point);
        }
    }

    /// <summary>
    /// 在Scene场景中,绘制Hermite曲线
    /// </summary>
    void OnDrawGizmos()
    {
        /*数据校正*/
        DadaCorrection();
        /*计算顶点*/
        ComputeNode();
        /*计算曲线*/
        DrawCurve();
    }

    /// <summary>
    /// 1. 非闭合曲线
    /// </summary>
    public void AddCurveNode()
    {
        nodePath.Insert(0, nodePath[0]);
        nodePath.Add(nodePath[nodePath.Count - 1]);
    }

    /// <summary>
    /// 2. 闭合曲线
    /// </summary>
    public void AddClosedCurveNode()
    {
        //nodePath.Insert(0, nodePath[0]);
        nodePath.Add(new NodePath(nodePath[0]));
        nodePath[nodePath.Count - 1].time = 1.0f;

        Vector3 vInitDir = (nodePath[1].point - nodePath[0].point).normalized;
        Vector3 vEndDir = (nodePath[nodePath.Count - 2].point - nodePath[nodePath.Count - 1].point).normalized;
        float firstLength = (nodePath[1].point - nodePath[0].point).magnitude;
        float lastLength = (nodePath[nodePath.Count - 2].point - nodePath[nodePath.Count - 1].point).magnitude;

        NodePath firstNode = new NodePath(nodePath[0]);
        firstNode.point = nodePath[0].point + vEndDir * firstLength;

        NodePath lastNode = new NodePath(nodePath[nodePath.Count - 1]);
        lastNode.point = nodePath[0].point + vInitDir * lastLength;

        nodePath.Insert(0, firstNode);
        nodePath.Add(lastNode);
    }

    /// <summary>
    /// 通过节点段数的时间大小,获取每段节点
    /// </summary>
    /// <param name="t"></param>
    /// <returns></returns>
    public Vector3 GetHermitAtTime(float t)
    {
        //Debug.Log(t);
        int k;
        //最后一个顶点
        if (t >= nodePath[nodePath.Count - 2].time)
        {
            return nodePath[nodePath.Count - 2].point;
        }
        for (k = 1; k < nodePath.Count-2; k++)
        {
            if (nodePath[k].time > t)
                break;
        }

        k = k - 1;
        float param = (t - nodePath[k].time) / (nodePath[k+1].time - nodePath[k].time);
        return GetHermitNode(k, param);
    }

    /// <summary>
    /// Herimite曲线:获取节点
    /// </summary>
    /// <param name="index">节点最近的顶点</param>
    /// <param name="t"></param>
    /// <returns></returns>
    public Vector3 GetHermitNode(int index,float t)
    {
        Vector3 v;
        Vector3 P0 = nodePath[index - 1].point;
        Vector3 P1 = nodePath[index].point;
        Vector3 P2 = nodePath[index + 1].point;
        Vector3 P3 = nodePath[index + 2].point;

        //调和函数
        float h1 = 2 * t * t * t - 3 * t * t + 1;
        float h2 = -2 * t * t * t + 3 * t * t;
        float h3 = t * t * t - 2 * t * t + t;
        float h4 = t * t * t - t * t;

        v = h1 * P1 + h2 * P2 + h3 * (P2 - P0) / smoothFactor + h4 * (P3 - P1) / smoothFactor;
        //Debug.Log(index + "  "+ t+"  "+v);
        return v;
    }
}

/// <summary>
/// 节点类
/// </summary>
public class NodePath
{
    public Vector3 point;
    public float time;

    public NodePath(Vector3 v,float t)
    {
        point = v;
        time = t;
    }

    public NodePath(NodePath n)
    {
        point = n.point;
        time = n.time;
    }
}

基类代码

using UnityEngine;
using System.Collections;

public class Curve_Root : MonoBehaviour {

    //曲线是否为闭合曲线
    public bool closedCurve = false;

    //曲线的颜色
    public Color _Color = Color.white;

    //自动跟随路径
    public bool AutoCurve = false;

    //旋转跟随
    public bool AutoRotate = false;
}

路径漫游

  在曲线函数中,参数t取值[0,1],将曲线进行分段。那么能够计算出每一个点的位置。因此,在路径漫游中,我们从原点出发,将t的增量作为下一个点位置,进行插值移动。就实现了路径漫游,同时进行朝向下一个顶点旋转,就可以使看的方向随着曲线变化。

Unity 3D 项目工程

http://download.csdn.net/detail/familycsd000/9365859

  

  

时间: 2024-10-31 09:06:32

Hermite (埃尔米特)曲线的相关文章

wiki-贝塞尔曲线

贝塞尔曲线 维基百科,自由的百科全书 三次方贝塞尔曲线 在数学的数值分析领域中,贝塞尔曲线, 又称贝赛尔曲线(Bézier曲线)是电脑图形学中相当重要的参数曲线.更高维度的广泛化贝塞尔曲线就称作贝塞尔曲面,其中贝塞尔三角是一种特殊的实例. 贝塞尔曲线于1962年,由法国工程师皮埃尔·贝塞尔(Pierre Bézier)所广泛发表,他运用贝塞尔曲线来为汽车的主体进行设计.贝塞尔曲线最初由Paul de Casteljau于1959年运用de Casteljau算法开发,以稳定数值的方法求出贝塞尔曲

样条之埃尔米特(Hermite)

埃尔米特(Charles Hermite,1822—1901) 法国数学家.巴黎综合工科学校毕业.曾任法兰西学院.巴黎高等师范学校.巴黎大学教授.法兰西科学院院士.在函数论.高等代数.微分方程等方面都有重要发现.1858年利用椭圆函数首先得出五次方程的解.1873年证明了自然对数的底e的超越性.在现代数学各分支中以他姓氏命名的概念(表示某种对称性)很多,如“埃尔米特二次型”.“埃尔米特算子”等. 这种算法是由上一节讲的CatmullRom演变而成. 关于插值与样条的介绍请看:http://www

OpenGL绘制简单的参数曲线——两点三次Hermite曲线(一)

网上这类曲线绘制的文章非常多,但是大多都是代码一贴就完事了,甚至连参数怎么调也没说清楚.我翻阅了不少资料,这里做个汇总,主要也就介绍一下几类简单的曲线绘制,如Hermite曲线.Bezier曲线等.今天先说说Hermite曲线,基本上最常见的就是两点三次的Hermite曲线了. 按照惯例,我们先来介绍一下Hermite曲线的原理.Hermite曲线是给定曲线段的两个端点坐标以及两端点处的切线矢量来描述的曲线.平面上一条三次参数曲线可以表示为: 图1 空间Hermite曲线跟上述类似,只是加一个z

UIBezierPath 贝塞尔曲线

1. UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(30, 30, 100, 100) cornerRadius:0]; CAShapeLayer * layer = [CAShapeLayer layer];    layer.path = path.CGPath;    layer.fillColor = [[UIColor blackColor]CGColor]; layer.strokeC

origin 8.5 曲线拟合,延长曲线范围

1. 输入数据并选择Y轴数据 2 非线性拟合 Analysis-Fitting-Nonlinear Curve Fit-Open Dialog 3.选择拟合曲线类型 在origin7.5中选择曲线类型和表达式比较直观,8.5好像反而不太直观了. 固定某个参数可在Parameters中设置. 点击Fit开始拟合 双击FitNL中的小图打开图像 4.延长拟合曲线范围 点击左上角小锁头图标,Change Parameters 切换回 选择Fitted Curves-Range改为Custom-去掉Au

iOS:使用贝塞尔曲线绘制图表(折线图、柱状图、饼状图)

1.介绍: UIBezierPath :画贝塞尔曲线的path类 UIBezierPath定义 : 贝赛尔曲线的每一个顶点都有两个控制点,用于控制在该顶点两侧的曲线的弧度. 曲线的定义有四个点:起始点.终止点(也称锚点)以及两个相互分离的中间点. 滑动两个中间点,贝塞尔曲线的形状会发生变化. UIBezierPath :对象是CGPathRef数据类型的封装,可以方便的让我们画出 矩形 . 椭圆 或者 直线和曲线的组合形状 初始化方法: + (instancetype)bezierPath; /

【开源项目解析】QQ“一键下班”功能实现解析——学习Path及贝塞尔曲线的基本使用

早在很久很久以前,QQ就实现了"一键下班"功能.何为"一键下班"?当你QQ有信息时,下部会有信息数量提示红点,点击拖动之后,就会出现"一键下班"效果.本文将结合github上关于此功能的一个简单实现,介绍这个功能的基本实现思路. 项目地址 https://github.com/chenupt/BezierDemo 最终实现效果 实现原理解析 我个人感觉,这个效果实现的很漂亮啊!那么咱们就来看看实现原理是什么~ 注:下面内容请参照项目源码观看. 其

用OpenGL进行曲线、曲面的绘制

实验目的 1)理解Bezier曲线.曲面绘制的基本原理:理解OpenGL中一维.二维插值求值器的用法. 2)掌握OpenGL中曲线.曲面绘图的方法,对比不同参数下的绘图效果差异: 代码1:用四个控制点绘制一条三次Bezier曲线 #include "stdafx.h" #include <stdlib.h> #include <time.h> #include <GL/glut.h> //4个控制点的3D坐标--z坐标全为0 GLfloat ctrl

HIT1946 希尔伯特分形曲线(dfs)

补第二次期末考的题--发现代码细节还需要加强啊--这样一道题一直犯小错误. 题目链接: http://acm.hit.edu.cn/hoj/problem/view?id=1946 题目描述: 希尔伯特分形曲线 Submitted : 53, Accepted : 16 数学家Hilbert曾发现一种十分奇特的曲线.一般的曲线是没有面积的,但他发现的这条曲线却能充满整个空间.Hilbert曲线是由不断的迭代过程形成的.如下图所示,最原始的曲线称为H1,由H1迭代形成H2,再由H2迭代形成H3..