Unity3D绘制两圆柱体相贯线

1 相贯线

  项目中需要用代码去绘制两个圆柱体相贯线,花了两天时间可算整明白怎么画了。国内有关这方面的文章不多,所以花了很多时间,我这里总结一下,如果能够帮助到需要的同学,也算善莫大焉。
  相贯线就是两个圆柱体相交表面所形成的曲线。如图。
相贯线示例
  最终使用Unity计算并绘制出的相贯线,如图。
Unity3D中计算出的相贯线效果图
  我们知道点构成线,线再构成面。特殊点可以构成特殊的曲线,特殊的曲线可以构成特殊的面。这个特殊是指,这些顶点的xyz坐标之间满足一定的关系,或者说满足一个方程。比方说顶点的 x x x y y y坐标满足 y = x 2 y=x^2 y=x2,那么当 z = 0 z=0 z=0时在空间中画出来就是一条抛物线。
z=0时的抛物线
  如果 z z z从-1到1之间变化,那画出来就是个抛物面。
抛物面
  所以我们只需把相贯线的方程算出来,然后就能够用代码把相贯线画出来。
  下面我将详细解释如何根据两个圆柱体的参数(位置、半径)去求解两个圆柱体的相贯线(需要一些矩阵方面的知识才能深刻理解背后的原理,不懂的强烈建议去学习一下,推荐《Unity Shader入门精要》第4章),然后会给出代码和工程去实现绘制相贯线。

2 求解相贯线

2.1 圆柱体方程

  我们知道,要列方程,必须先选定一个参考坐标系。只有选择了合适的参考系,我们才能很快地列出方程。如果参考系没选好,列方程将会变得很困难。对于圆柱体,我们一般把圆柱体轴线上的一个点当作原点O,然后把与圆柱底面平行的平面当作Oxz平面,圆柱的轴线当作y轴(这里的xyz我用的是Unity中的方向,大家可以随意选择的,不一定要按这个来设置)。
  我们知道二维坐标系(Oxz平面)下圆的方程为: x 2 + z 2 = R 2 x^2+z^2=R^2 x2+z2=R2
  然后这个圆沿着y轴不断移动,就形成圆柱体,所以这个圆柱体的方程如下
圆柱体方程
  现在我们另外选一个参考坐标系,这个参考坐标系的原心在圆柱轴线“右”边2个单位,坐标系变成O’x’y’z’,那么以Ox’y’z’为参考坐标系列出来的圆柱体方程就变为
坐标系往右移动2个单位后圆柱体的方程
  那么现在,请思考一个问题,Oxyz坐标系下的xyz值与O’x’y’z’坐标系下的值x’y’z’的数学关系是什么?
  在上面的例子中,我们能知道O’x’y’z’坐标可以理解为Oxyz坐标系往右移动了2个单位,所以就有方程
{ x + 2 = x ′ y = y ′ z = z ′ \begin{cases} x + 2 = x' \\ y = y' \\ z = z' \end{cases} x+2=xy=yz=z
  这一点很简单,但却特别重要(就是说两个参考坐标系的坐标,一定是存在一种转化关系的),下面部分将会深入一点探讨两个坐标系地相互转换问题。接下来我们看看怎么列相贯线的方程,同时看看如何求解。

2.2 求解相贯线方程

2.2.1 选择参考坐标系

  先设定一些参数。如图,两个圆柱体相交。大圆柱体的半径为R,小圆柱体的半径为r,然后两个圆柱体轴心的夹角为 α \alpha α,蓝色圆柱体偏移灰色圆柱体的距离为e。
相贯体参数示意图
  第一步,我们先把两个立方体的方程列出来,要列方程,就得先选择参考坐标系。这里选择参考坐标系就有讲究了,不能随便选。在2.1节中提到,我们一般把圆柱体轴线上的一个点当作原点O,然后把与圆柱底面平行的平面当作Oxz平面,圆柱的轴线当作y轴。但是在这里,我们不这样选择参考系,而是把参考坐标系 x y z xyz xyz轴的方向选为和Unity世界坐标的 x y z xyz xyz方向一样,为什么?主要是为了方便我们之后生成线的顶点(理论上参考坐标系无论怎么选都是可以的,但是这样选是最方便的)。
  灰色圆柱体的坐标系怎么选?
  先确定原心 O O O点。确定 O O O点分两个步骤,①蓝色圆柱体的轴线与灰色圆柱体圆心处水平切面会有一个交点,②把这个交点再平移到灰色圆柱体的轴线上,即得到 O O O点。
圆心O的选择
  然后取灰色圆柱体的轴线为 z z z轴,两圆柱体的公垂线为 x x x轴,垂直于 O x z Oxz Oxz平面同时朝向蓝色圆柱体一边的直线为 y y y轴。最终,灰色圆柱体的参考坐标系如下图。记为 O x y z Oxyz Oxyz
灰色圆柱体的参考坐标系
  这里强调一下,我们选择的参考坐标系 O x y z Oxyz Oxyz 与Unity的世界坐标系的方向是一模一样的,只是为了方便后面设置曲线的网格用。除此之外,两者没有任何关系! 这一点大家一定要记住并且理解。
   再来看蓝色圆柱体的参考坐标系怎么选。其实蓝色圆柱体的参考坐标系选择特别特别简单,就是把灰色圆柱体的参考坐标系旋转绕着x轴旋转一定角度(这里的角度大小是 180 ° − α 180°-α 180°α,后面部分我们还会看到旋转方向是有正负之分的,由于我们选择的参考坐标系是个左手坐标系,所以后面我们把这个角度带入矩阵计算时使用的是 − ( 180 ° − α ) -(180°-α) (180°α),这一点后面会详细说到),使得z轴与灰色圆柱体的轴线平行,然后将旋转后的坐标系平移到蓝色圆柱体的轴线与灰色圆柱体圆心处水平切面的交点(看上上图)处,就得到蓝色圆柱体的参考坐标系。具体过程如下图。
求参考坐标系
  灰色圆柱体最终的参考坐标系如下。我们记为 O ′ x ′ y ′ z ′ O'x'y'z' Oxyz
灰色圆柱体的坐标系
  两个坐标系放在一起如下。
两个坐标系

2.2.2 两个参考坐标系之间的变换

  那么问题来了,两个参考坐标系之间如何变换或者说如何将坐标系 O ′ x ′ y ′ z ′ O'x'y'z' Oxyz中的一个点 ( x ′ , y ′ , z ′ ) (x',y',z') (x,y,z)转换为坐标系 O x y z Oxyz Oxyz ( x , y , z ) (x,y,z) (x,y,z)点呢?你可能会问,我们为什么要知道这两个坐标系的关系呢,没啥原因,因为后面求解相贯线方程的时候需要用到这个转换关系。
  这里我就直接说结论了,详细的解释请参考冯乐乐的《Unity Shader入门精要》P69。

如果我们已知坐标系B的3个坐标轴在A坐标系下的表示 x → B \overrightarrow x_B x B y → B \overrightarrow y_B y B z → B \overrightarrow z_B z B,以及原点位置 O B O_B OB,那么坐标系B到坐标系A的变换矩阵为
M ⃗ B → A = [ ∣ ∣ ∣ ∣ x → B y → B z → B O B ∣ ∣ ∣ ∣ 0 0 0 1 ] \vec M_{B→A} = \begin{bmatrix} | & | & | & | \\ \overrightarrow x_B & \overrightarrow y_B & \overrightarrow z_B & O_{B} \\ | & | & | & | \\ 0 & 0 & 0 & 1 \end{bmatrix} M BA=x B0y B0z B0OB1
其中,| 表示按列展开。

  根据上面的结论,我们需要先要求出Ox’y’z’几个坐标轴 O ′ x ′ → \overrightarrow {O'x'} Ox O ′ y ′ → \overrightarrow {O'y'} Oy O ′ z ′ → \overrightarrow {O'z'} Oz 以及 O ′ O' O O x y z Oxyz Oxyz坐标系下的表示,然后才能写出将 O ′ x ′ y ′ z ′ O'x'y'z' Oxyz坐标系中的点变换到 O x y z Oxyz Oxyz坐标系下的变换矩阵。
  这一点我们很容易就能办到,因为从上一节中,我们知道 O ′ x ′ y ′ z ′ O'x'y'z' Oxyz实质是 O x y z Oxyz Oxyz先绕x轴旋转-(180°-α),然后再沿着 O x y z Oxyz Oxyz坐标系的x轴平移-e个单位得到的
  注意我的表述,先绕x轴旋转 − ( 180 ° − α ) -(180°-α) (180°α),为什么是 − ( 180 ° − α ) -(180°-α) (180°α),而不是 180 ° − α 180°-α 180°α呢?因为我们选择的参考坐标系 O x y z Oxyz Oxyz 是左手坐标系,左手坐标系应遵循左手法则,即绕x轴旋转时,左手大拇指朝向x轴正方向,然后其他4个手指握拳弯曲的方向即是绕x轴旋转的正方向 。很显然,虽然 O ′ z ′ → \overrightarrow {O'z'} Oz O z → \overrightarrow {Oz} Oz 之间的夹角大小为 180 ° − α 180°-α 180°α,但是我们在使用变换矩阵计算时应该代入 − ( 180 ° − α ) -(180°-α) (180°α)
  一个向量,绕着它的参考坐标系的 x x x轴旋转 θ \theta θ的变换矩阵为
M ⃗ 绕 x 轴 旋 转 θ 角 = [ 1 0 0 0 cos ⁡ θ − sin ⁡ θ 0 sin ⁡ θ cos ⁡ θ ] \vec M_{绕x轴旋转\theta角} = \begin{bmatrix} 1 & 0 & 0 \\ 0 & \cos\theta & -\sin\theta \\ 0 & \sin\theta & \cos\theta \end{bmatrix} M xθ=1000cosθsinθ0sinθcosθ
  将 − ( 180 ° − α ) -(180°-α) (180°α)代入 θ \theta θ得到绕 O x y z Oxyz Oxyz的x轴旋转-(180°-α)的变换矩阵为
M ⃗ 绕 O x y z 的 x 轴 旋 转 − ( 180 − α ) 度 = [ 1 0 0 0 cos ⁡ ( − 180 + α ) − sin ⁡ ( − 180 + α ) 0 sin ⁡ ( − 180 + α ) ) cos ⁡ ( − 180 + α ) ] = [ 1 0 0 0 − cos ⁡ α sin ⁡ α 0 − sin ⁡ α − cos ⁡ α ] \vec M_{绕Oxyz的x轴旋转-(180-\alpha)度} = \begin{bmatrix} 1 & 0 & 0 \\ 0 & \cos(-180+\alpha) & -\sin(-180+\alpha) \\ 0 & \sin(-180+\alpha) ) & \cos(-180+\alpha) \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 \\ 0 & -\cos\alpha & \sin \alpha \\ 0 & -\sin\alpha & -\cos\alpha \end{bmatrix} M Oxyzx(180α)=1000cos(180+α)sin(180+α))0sin(180+α)cos(180+α)=1000cosαsinα0sinαcosα

  因为 O ′ y ′ → \overrightarrow {O'y'} Oy O y → \overrightarrow {Oy} Oy (= [ 0 , 1 , 0 ] [0, 1, 0] [0,1,0])绕x轴旋转 − ( 180 ° − α ) -(180°-α) (180°α)后得到的,所以 O ′ y ′ → \overrightarrow {O'y'} Oy O x y z Oxyz Oxyz坐标系下的表示为
O ′ y ′ → = [ 1 0 0 0 − cos ⁡ α sin ⁡ α 0 − sin ⁡ α − cos ⁡ α ] [ 0 1 0 ] = [ 0 − cos ⁡ α − sin ⁡ α ] \overrightarrow {O'y'} = \begin{bmatrix} 1 & 0 & 0 \\ 0 & -\cos\alpha & \sin \alpha \\ 0 & -\sin\alpha & -\cos\alpha \end{bmatrix} \begin{bmatrix} 0 \\ 1 \\ 0 \\ \end{bmatrix} = \begin{bmatrix} 0 \\ -\cos\alpha \\ -\sin\alpha \\ \end{bmatrix} Oy =1000cosαsinα0sinαcosα010=0cosαsinα
  同理可得 O ′ z ′ → \overrightarrow {O'z'} Oz O x y z Oxyz Oxyz坐标系下的表示为
O ′ z ′ → = [ 1 0 0 0 − cos ⁡ α sin ⁡ α 0 − sin ⁡ α − cos ⁡ α ] [ 0 0 1 ] = [ 0 sin ⁡ α − cos ⁡ α ] \overrightarrow {O'z'} = \begin{bmatrix} 1 & 0 & 0 \\ 0 & -\cos\alpha & \sin \alpha \\ 0 & -\sin\alpha & -\cos\alpha \end{bmatrix} \begin{bmatrix} 0 \\ 0 \\ 1 \\ \end{bmatrix} = \begin{bmatrix} 0 \\ \sin\alpha \\ -\cos\alpha \\ \end{bmatrix} Oz =1000cosαsinα0sinαcosα001=0sinαcosα
   O ′ x ′ → \overrightarrow {O'x'} Ox O x y z Oxyz Oxyz坐标系下的表示与 O x → \overrightarrow {Ox} Ox 相同,仍然为
O ′ x ′ → = [ 1 0 0 ] \overrightarrow {O'x'} = \begin{bmatrix} 1 \\ 0 \\ 0 \\ \end{bmatrix} Ox =100
  另外, O ’ O’ O O O O点沿着 O x y z Oxyz Oxyz的x轴平移-e个单位得到的,所以 O ′ O' O O x y z Oxyz Oxyz坐标系下的表示为
O ′ = [ − e 0 0 ] O' = \begin{bmatrix} -e \\ 0 \\ 0 \\ \end{bmatrix} O=e00
  至此,我们便可根据冯乐乐书中给出的结论,直接写出从 O ′ x ′ y ′ z ′ O'x'y'z' Oxyz O x y z Oxyz Oxyz的变换矩阵
M ⃗ O ′ x ′ y ′ z ′ → O x y z = [ ∣ ∣ ∣ ∣ O ′ x ′ → O ′ y ′ → O ′ z ′ → O ′ → ∣ ∣ ∣ ∣ 0 0 0 1 ] = [ 1 0 0 − e 0 − cos ⁡ α sin ⁡ α 0 0 − sin ⁡ α − cos ⁡ α 0 0 0 0 1 ] \vec M_{O'x'y'z'→Oxyz} = \begin{bmatrix} | & | & | & | \\ \overrightarrow {O'x'} & \overrightarrow {O'y'} & \overrightarrow {O'z'} &\overrightarrow {O'} \\ | & | & | & | \\ 0 & 0 & 0 & 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & -e \\ 0 & -\cos\alpha & \sin\alpha & 0 \\ 0 & -\sin\alpha & -\cos\alpha & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} M OxyzOxyz=Ox 0Oy 0Oz 0O 1=10000cosαsinα00sinαcosα0e001
  即,坐标系 O ′ x ′ y ′ z ′ O'x'y'z' Oxyz中的点 ( x ′ , y ′ , z ′ ) (x',y',z') (x,y,z)变换为坐标系 O x y z Oxyz Oxyz中的 ( x , y , z ) (x,y,z) (x,y,z)点,满足的等式为
[ x y z 1 ] = [ 1 0 0 − e 0 − cos ⁡ α sin ⁡ α 0 0 − sin ⁡ α − cos ⁡ α 0 0 0 0 1 ] [ x ′ y ′ z ′ 1 ] \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & -e \\ 0 & -\cos\alpha & \sin\alpha & 0 \\ 0 & -\sin\alpha & -\cos\alpha & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x' \\ y' \\ z' \\ 1 \end{bmatrix} xyz1=10000cosαsinα00sinαcosα0e001xyz1
  即
[ x y z 1 ] = [ x ′ − e − y ′ cos ⁡ α + z ′ sin ⁡ α − y ′ sin ⁡ α − z ′ cos ⁡ α 1 ] \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} x' - e \\ - y' \cos\alpha + z' \sin\alpha \\ -y' \sin\alpha - z' \cos\alpha \\ 1 \end{bmatrix} xyz1=xeycosα+zsinαysinαzcosα1
  亦即
{ x = x ′ − e y = − y ′ cos ⁡ α + z ′ sin ⁡ α z = − y ′ sin ⁡ α − z ′ cos ⁡ α (1) \begin{cases} x = x' - e \\ y = - y' \cos\alpha + z' \sin\alpha \\ z = -y' \sin\alpha - z' \cos\alpha \\ \end{cases} \tag 1 x=xey=ycosα+zsinαz=ysinαzcosα(1)

2.2.3 相贯线方程

  根据2.1节,我们可知灰色圆柱体的方程为
{ x 2 + y 2 = R 2 z = z \begin{cases} x^2+y^2 = R^2 \\ z = z \end{cases} {x2+y2=R2z=z
  蓝色圆柱体的方程为
{ y ′ 2 + z ′ 2 = r 2 x ′ = x ′ \begin{cases} y'^2+z'^2 = r^2 \\ x' = x' \end{cases} {y2+z2=r2x=x
  相贯线的方程其实很简单,相贯线上的点只要同时满足以上两个圆柱体的方程就行了,即
{ x 2 + y 2 = R 2 x ′ 2 + y ′ 2 = r 2 (2) \begin{cases} x^2+y^2 = R^2 \\ x'^2+y'^2 = r^2 \\ \end{cases} \tag 2 {x2+y2=R2x2+y2=r2(2)
  由(1)式,我们可以解出
{ x ′ = x + e y ′ = − y cos ⁡ α − z sin ⁡ α \begin{cases} x' = x + e \\ y' = -y \cos \alpha - z \sin \alpha \end{cases} {x=x+ey=ycosαzsinα
  将 x ′ x' x y ′ y' y代入(2)式解得
{ y = ± R 2 − x 2 z = 1 sin ⁡ α ( ± r 2 − ( x + e ) 2 − y cos ⁡ α ) (3) \begin{cases} y = \pm \sqrt {R^2 - x^2}\\ z = \frac{1}{\sin \alpha}(\pm \sqrt{r^2 - (x+e)^2} - y \cos \alpha) \end{cases} \tag 3 {y=±R2x2 z=sinα1(±r2(x+e)2 ycosα)(3)
  其中, x ∈ [ − e − r , − e + r ] x \in [-e-r,-e+r] x[ere+r] α ∈ ( 0 ° , 180 ° ) α \in (0°,180°) α(0°180°)
  现在我们已经知道了 x x x的范围,只需将 x x x在此范围内不断变化,并代入公式(3)即可以算出 y y y z z z,也就求出了相贯线上所有点的坐标。
  你可能会问,为什么 x x x的范围范围是 [ − e − r , − e + r ] [-e-r,-e+r] [ere+r]呢?其实很简单,是根据正视图来的,这个相信大家一看图就明白,就不多说了。
x的取值范围

3 代码实现

  历经千辛万苦,终于把相贯线方程给列出来了。接下来就是写代码了。这个就不多说了,直接上码。
  项目链接:https://pan.baidu.com/s/17lWqUNDaRHnklZUWgKvB2Q
  提取码:d2u7

// 把画线的代码单独提出来看看,如果大家没使用过,去查查API吧。
mesh.vertices = vertices;
mesh.SetIndices(indices, MeshTopology.Lines, 0);

  完整代码如下。

using UnityEngine;

public class IntersectingLine : MonoBehaviour
{
    private Transform momPipe;                                                // 母管(灰色圆柱体).
    private Transform sonPipe;                                                // 子管(蓝色圆柱体).

    [Header("大圆柱体半径")]
    public float R;
    [Header("小圆柱体半径")]
    public float r;
    [Space(5)]
    public float e;
    public float alpha;

    private Mesh mesh;
    private MeshFilter mf;
    private Material lineMat;

    #region Unity_Method

    private void Start()
    {
        Init();

        GetParameter();
        SetOPos();
        CreateIntersectingLine(R, r, e, alpha, mesh);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            GetParameter();
            SetOPos();
            CreateIntersectingLine(R, r, e, alpha, mesh);
            Debug.Log("Recalculate intersecting line over.");
        }
    }

    #endregion

    #region Private_Method

    private void Init()
    {
        momPipe = GameObject.Find("MomPipe").transform;
        sonPipe = GameObject.Find("SonPipe").transform;

        mesh = new Mesh();
        MeshRenderer mr = gameObject.AddComponent<MeshRenderer>();
        mf = gameObject.AddComponent<MeshFilter>();
        mf.mesh = mesh;
        lineMat = new Material(Shader.Find("Unlit/Color"));
        lineMat.SetColor("_Color", Color.red);
        mr.material = lineMat;
    }

    /// <summary>
    /// 获取参数.
    /// </summary>
    private void GetParameter()
    {
        R = momPipe.localScale.x * 0.5f;
        r = sonPipe.localScale.x * 0.5f;
        e = Mathf.Abs(momPipe.position.x - sonPipe.position.x);
        alpha = Vector3.Angle(-momPipe.forward, sonPipe.forward);
    }

    /// <summary>
    /// 将自身挪动到O点.
    /// </summary>
    private void SetOPos()
    {
        // 把自身挪动至O点. 这一部分只适用于当前项目,如果灰色圆柱体有旋转需要另外处理。
        Vector3 dir = sonPipe.forward;
        // 根据蓝色圆柱体的轴线求出O'点对应的z值
        float z = -dir.z / dir.y * sonPipe.position.y + sonPipe.position.z;
        transform.position = new Vector3(momPipe.position.x, 0, z);
    }

    /// <summary>
    /// 创建相贯线.
    /// </summary>
    /// <param name="R">大圆柱体的半径.</param>
    /// <param name="r">小圆柱体的半径.</param>
    /// <param name="e">两圆柱体的偏移距离.</param>
    /// <param name="alpha">两圆柱体的夹角,角度值,范围为(0°, 180°).</param>
    /// <param name="mesh">相贯线的网格.</param>
    private bool CreateIntersectingLine(float R, float r, float e, float alpha, Mesh mesh)
    {
        if (R < r || (r + Mathf.Abs(e) > R) || alpha >= 180 || alpha <= 0)
        {
            Debug.LogError("Parameter error, please check." + " R: " + R.ToString("F1") + " r: " + r.ToString("F1") + " e: " + e.ToString("F1") + " alpha: " + alpha.ToString("F1"));
            return false;
        }

        int vertexCount = 100;                                                // 顶点数.
        Vector3[] vertices = new Vector3[vertexCount];
        int[] indices = new int[vertexCount * 2];

        float deltaRad = 2 * Mathf.PI / vertexCount;

        float alphaRad = alpha * Mathf.Deg2Rad;
        float sinAlpha = Mathf.Sin(alphaRad);
        float cosAlpha = Mathf.Cos(alphaRad);

        for (int i = 0; i < vertexCount; i++)
        {
            float rad = deltaRad * i;
            float x = -e + r * Mathf.Cos(rad);
            // 两个圆柱体相贯有两条相贯线,这里我们只取y为正的那一条
            float y = Mathf.Sqrt(R * R - x * x);
            // 取一下绝对值。由于float精度问题,当r = x+e 时,可能会使temp=-0.00000001,下面开方导致NaN
            float temp = Mathf.Abs(r * r - (x + e) * (x + e));
            float z = 0f;
            if (rad > Mathf.PI)
            {
                z = 1.0f / sinAlpha * (Mathf.Sqrt(temp) - y * cosAlpha);
            }
            else
            {
                z = 1.0f / sinAlpha * (-Mathf.Sqrt(temp) - y * cosAlpha);
            }

            // 注意,我们这里算出的x, y, z是以Oxyz坐标系为参考的,不是世界坐标更不是圆柱体的局部坐标.
            vertices[i] = new Vector3(x, y, z);

            indices[2 * i] = i;
            if (i == vertexCount - 1)
            {
                indices[2 * i + 1] = 0;
            }
            else
            {
                indices[2 * i + 1] = i + 1;
            }
        }

        mesh.vertices = vertices;
        mesh.SetIndices(indices, MeshTopology.Lines, 0);
        return true;
    }

    #endregion
}

  ps:这篇文章花了我周末两天时间,真心觉得写文章非常不容易,但是也发现写文章能够帮助自己大大加深对相关知识的理解,可能这也是大佬们坚持写技术博客的原因之一吧。希望能一直坚持下去,期待未来的自己。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值