贝塞尔曲线

该代码实现了一套用于圆滑处理折线的贝塞尔曲线算法,包括闭合和不闭合的折线处理。通过对控制点的计算和调整,使得折线的拐点变得更加平滑。方法包括计算特征点、生成控制点、应用贝塞尔曲线公式等步骤,适用于游戏开发中的路径设计或其他图形渲染场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、一阶推导二阶,三阶类似推导

一阶情况下就是,一条直线。

P0(X1,Y1)     P1(X2,Y2)

变量t 属于【0,1】

X2 = X1 + t *(X2-X1)

Y2 = Y1 + t  *( Y2-Y1)

则一阶式子: B1​(t)=P0​+(P1​−P0​)t 

当增加一个控制点 P2,则方程为  则线段有 P0P1 和 P1P2,一阶式代入,则方程就有:

B2​(t)​=(1−t)P0′​+tP1′​=(1−t)((1−t)P0​+tP1​)+t((1−t)P1​+tP2​)=(1−t)2P0​+2t(1−t)P1​+t2P2​​   ;

B2​(t)=(1−t)2P0​+2t(1−t)P1​+t2P2​,t∈[0,1]

三阶同样推理

/// <summary>
    /// 贝赛尔曲线拟合算法
    /// </summary>
    public class BezierMathLib
    {
        #region 圆滑整条折线
        /// <summary>
        /// 圆滑闭合折线
        /// </summary>
        /// <param name="originPoints">需要圆滑点的数组</param>
        /// <returns>Vector3数组</returns>
        public static Vector3[] CloseSpline(Vector3[] originPoints)
        {
            return CloseSpline(originPoints, 0.1, 0.6);
        }
        /// <summary>
        /// 圆滑闭合折线
        /// </summary>
        /// <param name="originPoints">需要圆滑点的数组</param>
        /// <param name="t">圆润参数,在0-1之间,值越小越圆滑</param>
        /// <returns>Vector3数组</returns>
        public static Vector3[] CloseSpline(Vector3[] originPoints, double t)
        {
            return CloseSpline(originPoints, t, 0.6);
        }
        /// <summary>
        /// 圆滑闭合折线
        /// </summary>
        /// <param name="originPoints">需要圆滑点的数组</param>
        /// <param name="t">圆润参数,在0-1之间,值越小越圆滑</param>
        /// <param name="scale">拉伸控制点值,在0-1之间</param>
        /// <returns>Vector3数组</returns>
        public static Vector3[] CloseSpline(Vector3[] originPoints, double t, double scale)
        {//计算特征点
            originPoints = CharacterPoints(originPoints);

            Vector3[] originPoint = null;
            if (originPoints[0].X == originPoints[originPoints.Length - 1].X && originPoints[0].Y == originPoints[originPoints.Length - 1].Y)
            {
                originPoint = new Vector3[originPoints.Length - 1];
                for (int i = 0; i < originPoint.Length; i++)
                    originPoint[i] = originPoints[i];
            }
            else
            {
                originPoint = originPoints;
            }

            int originCount = originPoint.Length;
            //控制点收缩系数 ,经调试0.6较好。
            //double scale = 0.3;
            Vector3[] midPoints = new Vector3[originCount];
            for (int i = 0; i < originCount; i++)
            {//生成中点   
                int nexti = (i + 1) % originCount;
                midPoints[i] = new Vector3();
                midPoints[i].X = (originPoint[i].X + originPoint[nexti].X) / 2.0f;
                midPoints[i].Y = (originPoint[i].Y + originPoint[nexti].Y) / 2.0f;
            }
            //平移中点
            Vector3[] extraPoints = new Vector3[2 * originCount];
            Vector3 midinmid = new Vector3();
            for (int i = 0; i < originCount; i++)
            {
                int nexti = (i + 1) % originCount;
                int backi = (i + originCount - 1) % originCount;
                midinmid.X = (midPoints[i].X + midPoints[backi].X) / 2.0f;
                midinmid.Y = (midPoints[i].Y + midPoints[backi].Y) / 2.0f;
                double offsetx = originPoint[i].X - midinmid.X;
                double offsety = originPoint[i].Y - midinmid.Y;
                int extraindex = 2 * i;
                extraPoints[extraindex] = new Vector3();
                extraPoints[extraindex].X = midPoints[backi].X + offsetx;
                extraPoints[extraindex].Y = midPoints[backi].Y + offsety;
                //朝 originPoint[i]方向收缩 
                double addx = (extraPoints[extraindex].X - originPoint[i].X) * scale;
                double addy = (extraPoints[extraindex].Y - originPoint[i].Y) * scale;
                extraPoints[extraindex].X = originPoint[i].X + addx;
                extraPoints[extraindex].Y = originPoint[i].Y + addy;

                int extranexti = (extraindex + 1) % (2 * originCount);
                extraPoints[extranexti] = new Vector3();
                extraPoints[extranexti].X = midPoints[i].X + offsetx;
                extraPoints[extranexti].Y = midPoints[i].Y + offsety;
                //朝 originPoint[i]方向收缩 
                addx = (extraPoints[extranexti].X - originPoint[i].X) * scale;
                addy = (extraPoints[extranexti].Y - originPoint[i].Y) * scale;
                extraPoints[extranexti].X = originPoint[i].X + addx;
                extraPoints[extranexti].Y = originPoint[i].Y + addy;
            }
            List<Vector3> getPoints = new List<Vector3>();
            Vector3[] controlPoint = new Vector3[4];
            Vector3 tempP = null;
            double px, py, pz;
            for (int i = 0; i < originCount; i++)
            {//生成4控制点,产生贝塞尔曲线
                controlPoint[0] = originPoint[i];
                int extraindex = 2 * i;
                controlPoint[1] = extraPoints[extraindex + 1];
                int extranexti = (extraindex + 2) % (2 * originCount);
                controlPoint[2] = extraPoints[extranexti];
                int nexti = (i + 1) % originCount;
                controlPoint[3] = originPoint[nexti];
                double u = 1;
                while (u >= 0)
                {
                    px = bezier3funcX(u, controlPoint);
                    py = bezier3funcY(u, controlPoint);
                    pz = bezier3funcZ(u, controlPoint);
                    tempP = new Vector3(px, py, pz);
                    getPoints.Add(tempP);//保存点
                    if (u == 0) break;
                    u -= t;
                    if (u < 0)
                    {
                        px = bezier3funcX(u, controlPoint);
                        py = bezier3funcY(u, controlPoint);
                        pz = bezier3funcZ(u, controlPoint);
                        tempP = new Vector3(px, py, pz);
                        getPoints.Add(tempP);//保存点
                        break;
                    }
                }
            }
            Vector3[] pointVector3 = new Vector3[getPoints.Count];
            getPoints.CopyTo(pointVector3, 0);
            getPoints = null;

            return pointVector3;
        }
        /// <summary>
        /// 圆滑不闭合的折线
        /// </summary>
        /// <param name="originPoints">需要圆滑点的数组</param>
        /// <returns>Vector3数组</returns>
        public static Vector3[] OpenSpline(Vector3[] originPoints)
        {
            return OpenSpline(originPoints, 0.2, 4);
        }
        /// <summary>
        /// 圆滑不闭合的折线
        /// </summary>
        /// <param name="originPoints">需要圆滑点的数组</param>
        /// <param name="t">圆润参数,在0-1之间,值越小越圆滑</param>
        /// <returns>Vector3数组</returns>
        public static Vector3[] OpenSpline(Vector3[] originPoints, double t)
        {
            return OpenSpline(originPoints, t, 4);
        }
        /// <summary>
        /// 圆滑不闭合的折线
        /// </summary>
        /// <param name="originPoints">需要圆滑点的数组</param>
        /// <param name="t">圆润参数,在0-1之间,值越小越圆滑</param>
        /// <param name="n">n,n值越大越平缓,n值小于需要圆滑点的个数</param>
        /// <returns>Vector3数组</returns>
        public static Vector3[] OpenSpline(Vector3[] originPoints, double t, int n)
        {
            int step = (int)(originPoints.Length / (n - 1));
            Vector3[] controlP = new Vector3[n];
            Vector3[] tempPoints = null;
            List<Vector3> getPoints = new List<Vector3>();
            bool sign = false;
            for (int i = 0; i < step; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    int index = i * (n - 1) + j;
                    if (index > originPoints.Length - 1)
                    {
                        sign = true;
                        List<Vector3> pp = new List<Vector3>();
                        for (int k = 0; k < controlP.Length; k++)
                        {
                            if (controlP[k] != null)
                                pp.Add(controlP[k]);
                        }
                        Vector3[] tempPP = new Vector3[pp.Count];
                        pp.CopyTo(tempPP, 0);
                        tempPoints = CreateBezier(tempPP, t);
                        getPoints.AddRange(tempPoints);
                        break;
                    }
                    controlP[j] = originPoints[index];
                }
                if (sign == true)
                    break;
                tempPoints = CreateBezier(controlP, t);
                getPoints.AddRange(tempPoints);
            }

            tempPoints = new Vector3[getPoints.Count];
            getPoints.CopyTo(tempPoints, 0);

            return tempPoints;
        }
        /// <summary>
        /// 计算方程的斜率
        /// </summary>
        /// <param name="vector1">方程的第一个点</param>
        /// <param name="vector2">方程的第二个点</param>
        /// <returns>得到方程的斜率</returns>
        private static double Derivative(Vector3 vector1, Vector3 vector2)
        {
            double k = double.MaxValue;
            if (vector2.X != vector1.X)
                k = (vector2.Y - vector1.Y) / (vector2.X - vector1.X);

            return k;
        }
        /// <summary>
        /// 提取特征点
        /// </summary>
        /// <param name="points">点列</param>
        /// <returns>Vector3</returns>
        private static Vector3[] CharacterPoints(Vector3[] points)
        {
            List<Vector3> characterP = new List<Vector3>();//保存特征点
            double[] k = new double[points.Length - 1];//保存斜率

            for (int i = 0; i < points.Length - 1; i++)
            {
                Vector3 pointCurr = points[i];
                Vector3 pointNext = points[i + 1];
                k[i] = Derivative(pointCurr, pointNext);
            }

            characterP.Add(points[0]);//加上第一个点
            for (int i = 0; i < k.Length - 1; i++)
            {
                if ((k[i] * k[i + 1]) <= 0)
                    characterP.Add(points[i + 1]);
            }
            characterP.Add(points[points.Length - 1]);//加上最后一个点

            Vector3[] pointTemp = new Vector3[characterP.Count];
            characterP.CopyTo(pointTemp, 0);

            return pointTemp;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uu"></param>
        /// <param name="controlP"></param>
        /// <returns></returns>
        private static double bezier3funcX(double uu, Vector3[] controlP)
        {
            double part0 = controlP[0].X * uu * uu * uu;
            double part1 = 3 * controlP[1].X * uu * uu * (1 - uu);
            double part2 = 3 * controlP[2].X * uu * (1 - uu) * (1 - uu);
            double part3 = controlP[3].X * (1 - uu) * (1 - uu) * (1 - uu);

            return part0 + part1 + part2 + part3;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uu"></param>
        /// <param name="controlP"></param>
        /// <returns></returns>
        private static double bezier3funcY(double uu, Vector3[] controlP)
        {
            double part0 = controlP[0].Y * uu * uu * uu;
            double part1 = 3 * controlP[1].Y * uu * uu * (1 - uu);
            double part2 = 3 * controlP[2].Y * uu * (1 - uu) * (1 - uu);
            double part3 = controlP[3].Y * (1 - uu) * (1 - uu) * (1 - uu);

            return part0 + part1 + part2 + part3;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uu"></param>
        /// <param name="controlP"></param>
        /// <returns></returns>
        private static double bezier3funcZ(double uu, Vector3[] controlP)
        {
            double part0 = controlP[0].Z * uu * uu * uu;
            double part1 = 3 * controlP[1].Z * uu * uu * (1 - uu);
            double part2 = 3 * controlP[2].Z * uu * (1 - uu) * (1 - uu);
            double part3 = controlP[3].Z * (1 - uu) * (1 - uu) * (1 - uu);

            return part0 + part1 + part2 + part3;
        }
        #endregion

        #region 圆滑折线拐点
        /// <summary>
        /// 圆滑不闭合折线之间的角
        /// </summary>
        /// <param name="points">构成折线的点数组</param>
        /// <returns>Vector3数组</returns>
        public static Vector3[] OpenInflexion(Vector3[] points)
        {
            return OpenInflexion(points, 0.15, 0.1);
        }
        /// <summary>
        /// 圆滑不闭合折线之间的角
        /// </summary>
        /// <param name="points">构成折线的点数组</param>
        /// <param name="pos">直线开始圆滑的位置,取值在0-0.5之间,超过0.5两直线之间会有交叉</param>
        /// <returns>Vector3数组</returns>
        public static Vector3[] OpenInflexion(Vector3[] points, double pos)
        {
            return OpenInflexion(points, pos, 0.1);
        }
        /// <summary>
        /// 圆滑不闭合折线之间的角
        /// </summary>
        /// <param name="points">构成折线的点数组</param>
        /// <param name="pos">直线开始圆滑的位置,取值在0-0.5之间,超过0.5两直线之间会有交叉</param>
        /// <param name="t">圆润参数,在0-1之间,值越小越圆滑</param>
        /// <returns>Vector3数组</returns>
        public static Vector3[] OpenInflexion(Vector3[] points, double pos, double t)
        {
            if (points == null || points.Length == 0) return new Vector3[0];

            //保存不在一条直线上的所有点
            List<Vector3> characterP = new List<Vector3>();
            double[] k = new double[points.Length - 1];//保存斜率
            for (int i = 0; i < points.Length - 1; i++)
            {
                Vector3 pointCurr = points[i];
                Vector3 pointNext = points[i + 1];
                k[i] = Derivative(pointCurr, pointNext);
            }
            characterP.Add(points[0]);//加上第一个点
            for (int i = 0; i < k.Length - 1; i++)
            {
                characterP.Add(points[i + 1]);
                if ((k[i] - k[i + 1] == 0))
                {
                    int lastValue = characterP.Count - 1;
                    characterP.RemoveAt(lastValue);
                }
            }
            characterP.Add(points[points.Length - 1]);//加上最后一个点

            //根据pos值计算拐点在直线上的位置            
            Vector3 tempPoint = null;
            List<Vector3> getPoints = new List<Vector3>();
            Vector3 dir = null, normalize = null;

            for (int i = 0; i < characterP.Count - 1; i++)
            {
                getPoints.Add(characterP[i]);

                Vector3 pointCurr = characterP[i];
                Vector3 pointNext = characterP[i + 1];

                dir = pointNext - pointCurr;
                double len = dir.Length();
                normalize = dir.NormalisedCopy();//.Normalise();//.Normalize();

                tempPoint = pointCurr + normalize * (len * pos);
                getPoints.Add(tempPoint);

                tempPoint = pointCurr + normalize * (len * (1 - pos));
                getPoints.Add(tempPoint);
            }
            getPoints.Add(characterP[characterP.Count - 1]);//加上最后一个点
            getPoints.RemoveAt(1);
            getPoints.RemoveAt(getPoints.Count - 2);

            //处理拐点,使拐点变圆滑
            List<Vector3> pointss = new List<Vector3>();
            Vector3[] controlPoints = new Vector3[3];
            Vector3 point = null;

            pointss.Add(getPoints[0]);
            for (int i = 0; i < characterP.Count - 2; i++)
            {//获取控制点
                controlPoints[0] = getPoints[i * 3 + 1];
                controlPoints[1] = getPoints[i * 3 + 2];
                controlPoints[2] = getPoints[i * 3 + 3];

                double u = 0;
                while (u <= 1)
                {
                    point = Bezier2Func(u, controlPoints);
                    if (pointss[pointss.Count - 1].X != point.X || pointss[pointss.Count - 1].Y != point.Y)
                    {
                        pointss.Add(point);
                    }
                    if (u == 1) break;
                    u += t;
                    if (u > 1)
                    {
                        u = 1;
                        point = Bezier2Func(u, controlPoints);
                        if (pointss[pointss.Count - 1].X != point.X || pointss[pointss.Count - 1].Y != point.Y)
                        {
                            pointss.Add(point);
                        }
                        break;
                    }
                }
            }
            pointss.Add(getPoints[getPoints.Count - 1]);

            Vector3[] tempPs = new Vector3[pointss.Count];
            pointss.CopyTo(tempPs, 0);

            return tempPs;
        }
        /// <summary>
        /// 圆滑闭合折线之间的角
        /// </summary>
        /// <param name="pointArray">构成折线的点数组</param>
        /// <returns>Vector3数组</returns>
        public static Vector3[] CloseInflexion(Vector3[] pointArray)
        {
            return CloseInflexion(pointArray, 0.15, 0.1);
        }
        /// <summary>
        /// 圆滑闭合折线之间的角
        /// </summary>
        /// <param name="pointArray">构成折线的点数组</param>
        /// <param name="pos">直线开始圆滑的位置,取值在0-0.5之间,超过0.5两直线之间会有交叉</param>
        /// <returns>Vector3数组</returns>
        public static Vector3[] CloseInflexion(Vector3[] pointArray, double pos)
        {
            return CloseInflexion(pointArray, pos, 0.1);
        }
        /// <summary>
        /// 圆滑闭合折线之间的角
        /// </summary>
        /// <param name="pointArray">构成折线的点数组</param>
        /// <param name="pos">直线开始圆滑的位置,取值在0-0.5之间,超过0.5两直线之间会有交叉</param>
        /// <param name="t">圆润参数,在0-1之间,值越小越圆滑</param>
        /// <returns>Vector3数组</returns>
        public static Vector3[] CloseInflexion(Vector3[] pointArray, double pos, double t)
        {
            Vector3[] points = null;
            if (pointArray[0].X != pointArray[pointArray.Length - 1].X && pointArray[0].Y != pointArray[pointArray.Length - 1].Y)
            {
                points = new Vector3[pointArray.Length + 1];
                pointArray.CopyTo(points, 0);
                points[pointArray.Length] = points[0];
            }
            else
            {
                points = pointArray;
            }

            //保存不在一条直线上的所有点
            List<Vector3> characterP = new List<Vector3>();
            double[] k = new double[points.Length - 1];//保存斜率
            for (int i = 0; i < points.Length - 1; i++)
            {
                Vector3 pointCurr = points[i];
                Vector3 pointNext = points[i + 1];
                k[i] = Derivative(pointCurr, pointNext);
            }
            characterP.Add(points[0]);//加上第一个点
            for (int i = 0; i < k.Length - 1; i++)
            {
                characterP.Add(points[i + 1]);
                if ((k[i] - k[i + 1] == 0))
                {
                    int lastValue = characterP.Count - 1;
                    characterP.RemoveAt(lastValue);
                }
            }
            characterP.Add(points[points.Length - 1]);//加上最后一个点

            //根据pos值计算拐点在直线上的位置            
            Vector3 tempPoint = null;
            List<Vector3> getPoints = new List<Vector3>();
            Vector3 dir = null, normalize = null;

            for (int i = 0; i < characterP.Count - 1; i++)
            {
                getPoints.Add(characterP[i]);

                Vector3 pointCurr = characterP[i];
                Vector3 pointNext = characterP[i + 1];

                dir = pointNext - pointCurr;
                double len = dir.Length();
                normalize = dir.NormalisedCopy();//.Normalize();

                tempPoint = pointCurr + normalize * (len * pos);
                getPoints.Add(tempPoint);

                tempPoint = pointCurr + normalize * (len * (1 - pos));
                getPoints.Add(tempPoint);
            }

            //处理拐点,使拐点变圆滑
            List<Vector3> pointss = new List<Vector3>();
            Vector3[] controlPoints = new Vector3[3];
            Vector3 point = null;

            for (int i = 0; i < characterP.Count - 1; i++)
            {
                //获取控制点
                if (i == characterP.Count - 2)
                {
                    controlPoints[0] = getPoints[getPoints.Count - 1];
                    controlPoints[1] = getPoints[0];
                    controlPoints[2] = getPoints[1];
                }
                else
                {
                    controlPoints[0] = getPoints[i * 3 + 2];
                    controlPoints[1] = getPoints[i * 3 + 3];
                    controlPoints[2] = getPoints[i * 3 + 4];
                }

                double u = 0;
                while (u <= 1)
                {
                    point = Bezier2Func(u, controlPoints);
                    pointss.Add(point);
                    //u的步长决定曲线的疏密
                    if (u == 1) break;
                    u += t;
                    if (u > 1)
                    {
                        u = 1;
                        point = Bezier2Func(u, controlPoints);
                        pointss.Add(point);
                        break;
                    }
                }
            }
            pointss.Add(getPoints[2]);

            Vector3[] tempPs = new Vector3[pointss.Count];
            pointss.CopyTo(tempPs, 0);

            return tempPs;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="uu"></param>
        /// <param name="controlP"></param>
        /// <returns></returns>
        private static Vector3 Bezier2Func(double uu, Vector3[] controlP)
        {
            double x0, y0, z0;
            double partX0 = controlP[0].X * (1 - uu) * (1 - uu);
            double partX1 = 2 * controlP[1].X * uu * (1 - uu);
            double partX2 = controlP[2].X * uu * uu;
            x0 = partX0 + partX1 + partX2;

            double partY0 = controlP[0].Y * (1 - uu) * (1 - uu);
            double partY1 = 2 * controlP[1].Y * uu * (1 - uu);
            double partY2 = controlP[2].Y * uu * uu;
            y0 = partY0 + partY1 + partY2;

            double partZ0 = controlP[0].Z * (1 - uu) * (1 - uu);
            double partZ1 = 2 * controlP[1].Z * uu * (1 - uu);
            double partZ2 = controlP[2].Z * uu * uu;
            z0 = partZ0 + partZ1 + partZ2;

            Vector3 point = new Vector3(x0, y0, z0);

            return point;
        }
        #endregion

        #region 私有函数
        /// <summary>
        /// 
        /// </summary>
        /// <param name="controlPoints"></param>
        /// <param name="t"></param>
        /// <returns></returns>
        private static Vector3[] CreateBezier(Vector3[] controlPoints, double t)
        {
            List<Vector3> getPoints = new List<Vector3>();
            Vector3 point = null;
            int n = controlPoints.Length - 1;
            double u = 0;
            while (u <= 1)
            {
                point = Bezier(n, u, controlPoints);
                getPoints.Add(point);
                if (u == 1) break;
                u += t;
                if (u > 1)
                {
                    u = 1;
                    point = Bezier(n, u, controlPoints);
                    getPoints.Add(point);
                    break;
                }
            }

            Vector3[] points = new Vector3[getPoints.Count];
            getPoints.CopyTo(points, 0);

            return points;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="n"></param>
        /// <param name="t"></param>
        /// <param name="controlPoints"></param>
        /// <returns></returns>
        private static Vector3 Bezier(int n, double t, Vector3[] controlPoints)
        {
            double x0 = 0, y0 = 0, z0 = 0;
            Vector3 point = null;
            for (int i = 0; i < controlPoints.Length; i++)
            {
                point = Bernstein(i, n, t, controlPoints[i]);
                x0 = x0 + point.X;
                y0 = y0 + point.Y;
                z0 = z0 + point.Z;
            }

            x0 = System.Math.Round(x0, 15);
            y0 = System.Math.Round(y0, 15);
            z0 = System.Math.Round(z0, 15);
            point = new Vector3(x0, y0, z0);

            return point;
        }
        /// <summary>
        /// Bi,n(t)是n次Bernstein基函数。
        /// </summary>
        /// <param name="i"></param>
        /// <param name="n"></param>
        /// <param name="t"></param>
        /// <param name="controlPoint"></param>
        /// <returns></returns>
        private static Vector3 Bernstein(int i, int n, double t, Vector3 controlPoint)
        {
            double N = 1, T = 1;
            Vector3 point = new Vector3();
            int j, Nn = 1, Ni = 1, Nni = 1;
            for (j = n; j >= 1; j--) Nn = Nn * j;
            for (j = i; j >= 1; j--) Ni = Ni * j;
            for (j = n - i; j >= 1; j--) Nni = Nni * j;
            N = Nn / (double)(Ni * Nni);

            T = System.Math.Pow(t, i) * System.Math.Pow((1 - t), (n - i));

            point.X = controlPoint.X * N * T;
            point.Y = controlPoint.Y * N * T;
            point.Z = controlPoint.Z * N * T;

            return point;
        }
        #endregion

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值