Auxiliary tool: cylinder drawing

      After the function is finished after get off work, write an article and record it, and there will be in-depth research in the follow-up.
      Assuming that there are two points in the three-dimensional space, a cylinder is generated with these two points as the endpoints, as follows: the
insert image description here
      two ends f and t in the space form a cylinder, then we must first obtain the plane where f and t are located, and then find all the vertices of the circular grid on the plane. The space circle has been explained in detail in the previous article. Here we directly write the code to calculate the coordinates of all vertices, as follows:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PointCylinderRender : MonoBehaviour
{
    
    
    [Range(0, 20f)]
    public float circleRadius = 10f;
    [Range(3, 50)]
    public int circleSegement = 20;
    public Transform startTrans;            //from
    public Transform endTrans;              //to
    [Range(0, 90f)]
    public float rotateAngle = 30f;

    void Start()
    {
    
    

    }

    void Update()
    {
    
    
        DrawCylinderGizmos();
    }
    /// <summary>
    /// 绘制圆柱体网线图
    /// </summary>
    private void DrawCylinderGizmos()
    {
    
    
        Vector3 start = startTrans.position;
        Vector3 end = endTrans.position;
        Vector3 p2 = RotateAroundXAxis(start, end, rotateAngle * Mathf.Deg2Rad);
        Vector3 p1 = RayLineCrossPanel(start, end, p2);
        Vector3 p = start + (p1 - start).normalized * circleRadius;
        Vector3[] startposarr = CalculateCirclePoints(start, end, p, circleSegement);
        Vector3[] endposarr = CalculateBiasPoints(start, end, startposarr);
        for (int i = 0; i < circleSegement; i++)
        {
    
    
            Vector3 ccspos = startposarr[i];
            Vector3 ccsposp1 = startposarr[(i + 1) % circleSegement];
            Vector3 ccepos = endposarr[i];
            Vector3 cceposp1 = endposarr[(i + 1) % circleSegement];
            //构建start平面圆形
            Debug.DrawLine(ccspos, ccsposp1, Color.black);
            //构建start圆形的辐条
            Debug.DrawLine(start, ccspos, Color.black);
            //构建start->end界面矩形
            Debug.DrawLine(ccspos, ccepos, Color.black);
            //构建end平面圆形
            Debug.DrawLine(ccepos, cceposp1, Color.black);
            //构建end圆形的辐条
            Debug.DrawLine(end, ccepos, Color.black);
        }
    }

    /// <summary>
    /// 已知start为圆心r半径圆环上所有离散坐标sposarr
    /// 只需要通过sposarr+=(end-start)即可得到tposarr
    /// </summary>
    /// <param name="start"></param>
    /// <param name="end"></param>
    /// <param name="sposarr"></param>
    /// <returns></returns>
    private Vector3[] CalculateBiasPoints(Vector3 start, Vector3 end, Vector3[] sposarr)
    {
    
    
        Vector3[] eposarr = new Vector3[sposarr.Length];
        Vector3 offset = end - start;
        for (int i = 0; i < sposarr.Length; i++)
        {
    
    
            Vector3 spos = sposarr[i];
            Vector3 epos = spos + offset;
            eposarr[i] = epos;
        }
        return eposarr;
    }

    /// <summary>
    /// 算出圆环上所有离散坐标点
    /// </summary>
    /// <param name="start"></param>
    /// <param name="end"></param>
    /// <param name="p"></param>
    /// <param name="sege"></param>
    /// <returns></returns>
    private Vector3[] CalculateCirclePoints(Vector3 start, Vector3 end, Vector3 p, int sege)
    {
    
    
        Vector3[] posarr = new Vector3[sege];
        posarr[0] = p;
        Vector3 naxis = (end - start).normalized;
        float segerad = 2f * Mathf.PI / (float)sege;
        for (int i = 1; i < sege; i++)
        {
    
    
            float rad = segerad * i;
            Vector3 segepos = RotateAroundAnyAxis(start, p, naxis, rad);
            posarr[i] = segepos;
        }
        return posarr;
    }

    /// <summary>
    /// p(x,y,z)点绕start为起点的任意坐标轴旋转后的坐标
    /// </summary>
    /// <param name="start"></param>
    /// <param name="naxis"></param>
    /// <param name="rad"></param>
    /// <returns></returns>
    private Vector3 RotateAroundAnyAxis(Vector3 start, Vector3 p, Vector3 naxis, float rad)
    {
    
    
        float n1 = naxis.x;
        float n2 = naxis.y;
        float n3 = naxis.z;

        //获取p相对start的本地坐标
        p -= start;

        float sin = Mathf.Sin(rad);
        float cos = Mathf.Cos(rad);

        Matrix3x3 mat = new Matrix3x3();

        mat.m00 = n1 * n1 * (1 - cos) + cos;
        mat.m01 = n1 * n2 * (1 - cos) - n3 * sin;
        mat.m02 = n1 * n3 * (1 - cos) + n2 * sin;

        mat.m10 = n1 * n2 * (1 - cos) + n3 * sin;
        mat.m11 = n2 * n2 * (1 - cos) + cos;
        mat.m12 = n2 * n3 * (1 - cos) - n1 * sin;

        mat.m20 = n1 * n3 * (1 - cos) - n2 * sin;
        mat.m21 = n2 * n3 * (1 - cos) + n1 * sin;
        mat.m22 = n3 * n3 * (1 - cos) + cos;

        //绕轴旋转后,处理成世界坐标
        Vector3 px = mat * p + start;

        return px;
    }

    /// <summary>
    /// 通过start end计算start所处平面F方程
    /// 通过end p2计算射线与平面F交点p1
    /// </summary>
    /// <param name="start"></param>
    /// <param name="end"></param>
    /// <param name="p2"></param>
    /// <returns></returns>
    private Vector3 RayLineCrossPanel(Vector3 start, Vector3 end, Vector3 p2)
    {
    
    
        //start = from
        //end = to
        //构建平面F方程参数
        Vector3 ft = end - start;
        float u = ft.x, v = ft.y, w = ft.z;
        float a = start.x, b = start.y, c = start.z;
        //构建射线tp2参数
        float sx = end.x;
        float sy = end.y;
        float sz = end.z;
        Vector3 ntp2 = (p2 - end).normalized;
        float dx = ntp2.x;
        float dy = ntp2.y;
        float dz = ntp2.z;
        //计算p1
        float n = ((u * a + v * b + w * c) - (u * sx + v * sy + w * sz)) / (u * dx + v * dy + w * dz);
        Vector3 p1 = end + n * ntp2;
        return p1;
    }

    /// <summary>
    /// 空间任意起终点
    /// 终点绕x轴旋转rad弧度
    /// </summary>
    /// <param name="start"></param>
    /// <param name="end"></param>
    /// <param name="rad"></param>
    /// <returns></returns>
    private Vector3 RotateAroundXAxis(Vector3 start, Vector3 end, float rad)
    {
    
    
        Matrix3x3 mat = new Matrix3x3();

        float cos = Mathf.Cos(rad);
        float sin = Mathf.Sin(rad);

        mat.m00 = 1;
        mat.m01 = 0;
        mat.m02 = 0;
        mat.m10 = 0;
        mat.m11 = cos;
        mat.m12 = -sin;
        mat.m20 = 0;
        mat.m21 = sin;
        mat.m22 = cos;

        Vector3 ret = mat * (start - end) + end;
        return ret;
    }
}

      The effect is as follows:
insert image description here
      the network line rendering is drawn correctly.
      Next, we use meshfilter to build the grid, as follows:
insert image description here
      You can see the triangles topology of the cylinder. Of course, for the sake of illustration, we use a "circle" with four vertices to start writing code:

private MeshRenderer meshRender;
    private MeshFilter meshFilter;

    private float lastCcRadius;
    private int lastCcSege;
    private Vector3 lastStartPos;
    private Vector3 lastEndPos;

    private Mesh mesh;

    void Start()
    {
    
    
        meshRender = GetComponent<MeshRenderer>();
        meshFilter = GetComponent<MeshFilter>();
        mesh = new Mesh();
    }

    void Update()
    {
    
    
        if (CheckParamsChanged())
        {
    
    
            UpdateCylinderMesh();
        }
    }
    /// <summary>
    /// 检查绘制参数改变
    /// 改变才重绘制
    /// </summary>
    /// <returns></returns>
    private bool CheckParamsChanged()
    {
    
    
        if (lastCcRadius != circleRadius
            || lastCcSege != circleSegement
            || lastStartPos != startTrans.position
            || lastEndPos != endTrans.position)
        {
    
    
            lastCcRadius = circleRadius;
            lastCcSege = circleSegement;
            lastStartPos = startTrans.position;
            lastEndPos = endTrans.position;
            return true;
        }
        return false;
    }

    /// <summary>
    /// 绘制圆柱体网格
    /// </summary>
    private void UpdateCylinderMesh()
    {
    
    
        Vector3 start = startTrans.position;
        Vector3 end = endTrans.position;
        Vector3 p2 = RotateAroundXAxis(start, end, rotateAngle * Mathf.Deg2Rad);
        Vector3 p1 = RayLineCrossPanel(start, end, p2);
        Vector3 p = start + (p1 - start).normalized * circleRadius;
        Vector3[] startposarr = CalculateCirclePoints(start, end, p, circleSegement);
        Vector3[] endposarr = CalculateBiasPoints(start, end, startposarr);
        //构建网格数据
        if (mesh != null)
        {
    
    
            mesh.Clear();
        }
        //构建顶点列表
        List<Vector3> vertlist = new List<Vector3>();
        vertlist.Add(start);
        vertlist.AddRange(startposarr);
        vertlist.AddRange(endposarr);
        vertlist.Add(end);
        //构建拓扑三角列表(逆时针)
        //014 043 032 021
        List<int> trilist = new List<int>();
        for (int i = 0; i < circleSegement; i++)
        {
    
    
            int[] tris = new int[]
            {
    
    
                0,
                i+2>circleSegement?(i+2)%circleSegement:i+2,
                i+1
            };
            trilist.AddRange(tris);
        }
        //165 126
        //276 237
        //387 348
        //458 415
        for (int i = 0; i < circleSegement; i++)
        {
    
    
            int[] tris = new int[]
            {
    
    
                i+1,
                i+circleSegement+2>circleSegement*2?i+2:i+circleSegement+2,
                i+circleSegement+1
            };
            trilist.AddRange(tris);
            tris = new int[]
            {
    
    
                i+1,
                i+2>circleSegement?(i+2)%circleSegement:i+2,
                i+circleSegement+2>circleSegement*2?i+2:i+circleSegement+2
            };
            trilist.AddRange(tris);
        }
        //956 967 978 985
        for (int i = 0; i < circleSegement; i++)
        {
    
    
            int[] tris = new int[]
            {
    
    
                circleSegement*2+1,
                i+circleSegement+1,
                i+circleSegement+2>circleSegement*2?i+2:i+circleSegement+2
            };
            trilist.AddRange(tris);
        }
        //构建网格
        mesh.vertices = vertlist.ToArray();
        mesh.triangles = trilist.ToArray();
        meshFilter.sharedMesh = mesh;
    }

      What needs to be paid attention to in the code is the construction of the topological triangle index, the effect is as follows:
insert image description here

      It can be seen that the grid construction is ok, but there is a problem with the lighting, because the normal vector is missing, we have to build the normal vector, as follows: The
insert image description here
      diagram should be easy to understand, and then write the code:

//构建法向量
        List<Vector3> normlist = new List<Vector3>();
        Vector3 nf = (start - end).normalized;
        normlist.Add(nf);
        Vector3[] nfarr = new Vector3[circleSegement];
        Vector3[] ntarr = new Vector3[circleSegement];
        for (int i = 0; i < circleSegement; i++)
        {
    
    
            nfarr[i] = (startposarr[i] - start).normalized;
            ntarr[i] = (endposarr[i] - end).normalized;
        }
        normlist.AddRange(nfarr);
        normlist.AddRange(ntarr);
        Vector3 nt = (end - start).normalized;
        normlist.Add(nt);

      Next, witness a magical moment:
insert image description here      Hahahaha, it can be seen that the cross-section of the cylinder is rendered normally, and the two ends are rendered like a hemisphere, which is of course! Because the method vector of the circular plane at both ends is obtained by the NFARR and the NF linear interpolation of the NFARR and the vertical plane F of the parallel plane F. This becomes a hemisphere. In fact, the UNITY cylindrical body is composed of circular and middle section rectangles at both ends. The question is as follows: It can be seen that the Cylinder 88Verts 80tris comes with Unity, which means that the plane round and middle section rectangle of both ends are "disconnected", so you have to re -process the Mesh, as follows: continue to
insert image description here
      modify
insert image description here      :

    /// <summary>
    /// 绘制断裂的圆柱体网格
    /// </summary>
    private void UpdateBreakedCylinderMesh()
    {
    
    
        Vector3 start = startTrans.position;
        Vector3 end = endTrans.position;
        Vector3 p2 = RotateAroundXAxis(start, end, rotateAngle * Mathf.Deg2Rad);
        Vector3 p1 = RayLineCrossPanel(start, end, p2);
        Vector3 p = start + (p1 - start).normalized * circleRadius;
        Vector3[] startposarr = CalculateCirclePoints(start, end, p, circleSegement);
        Vector3[] endposarr = CalculateBiasPoints(start, end, startposarr);
        //构建网格数据
        if (mesh != null)
        {
    
    
            mesh.Clear();
        }
        //构建顶点列表
        List<Vector3> vertlist = new List<Vector3>();
        //起点圆形
        vertlist.Add(start);
        vertlist.AddRange(startposarr);
        //中间截面矩形
        vertlist.AddRange(startposarr);
        vertlist.AddRange(endposarr);
        //终点圆形
        vertlist.AddRange(endposarr);
        vertlist.Add(end);
        //构建拓扑三角列表(逆时针)
        //起点圆形
        //014 043 032 021
        List<int> trilist = new List<int>();
        for (int i = 0; i < circleSegement; i++)
        {
    
    
            int[] tris = new int[]
            {
    
    
                0,
                i+2>circleSegement?(i+2)%circleSegement:i+2,
                i+1
            };
            trilist.AddRange(tris);
        }
        //中间截面
        //5 10 9    5 6 10
        //6 11 10   6 7 11
        //7 12 11   7 8 12
        //8 9  12   8 5 9
        for (int i = 0; i < circleSegement; i++)
        {
    
    
            int[] tris = new int[]
            {
    
    
                i+circleSegement+1,
                i+circleSegement*2+2>circleSegement*3?i+circleSegement+2:i+circleSegement*2+2,
                i+circleSegement*2+1
            };
            trilist.AddRange(tris);
            tris = new int[]
            {
    
    
                i+circleSegement+1,
                i+circleSegement+2>circleSegement*2?(i+circleSegement+2)%circleSegement+circleSegement:i+circleSegement+2,
                i+circleSegement*2+2>circleSegement*3?i+circleSegement+2:i+circleSegement*2+2
            };
            trilist.AddRange(tris);
        }
        //终点圆形
        //17 13 14   
        //17 14 15
        //17 15 16
        //17 16 13
        for (int i = 0; i < circleSegement; i++)
        {
    
    
            int[] tris = new int[]
            {
    
    
                circleSegement*4+1,
                i+circleSegement*3+1,
                i+circleSegement*3+2>circleSegement*4?i+circleSegement*2+2:i+circleSegement*3+2
            };
            trilist.AddRange(tris);
        }
        //构建法向量
        List<Vector3> normlist = new List<Vector3>();
        Vector3 nf = (start - end).normalized;
        normlist.Add(nf);
        Vector3[] nfs = new Vector3[circleSegement];
        for (int i = 0; i < circleSegement; i++)
        {
    
    
            nfs[i] = nf;
        }
        normlist.AddRange(nfs);
        Vector3[] nfarr = new Vector3[circleSegement];
        Vector3[] ntarr = new Vector3[circleSegement];
        for (int i = 0; i < circleSegement; i++)
        {
    
    
            nfarr[i] = (startposarr[i] - start).normalized;
            ntarr[i] = (endposarr[i] - end).normalized;
        }
        normlist.AddRange(nfarr);
        normlist.AddRange(ntarr);
        Vector3 nt = (end - start).normalized;
        Vector3[] nts = new Vector3[circleSegement];
        for (int i = 0; i < circleSegement; i++)
        {
    
    
            nts[i] = nt;
        }
        normlist.AddRange(nts);
        normlist.Add(nt);
        //构建网格
        mesh.vertices = vertlist.ToArray();
        mesh.triangles = trilist.ToArray();
        mesh.normals = normlist.ToArray();
        meshFilter.sharedMesh = mesh;
    }

      Solved the problem of normal vector lighting:
insert image description here
      At this point, a cylinder has been roughly drawn, but there are still problems that need to be explained and solved:
      1. How to calculate the uv parameters?
insert image description here
      The uv mapping of the cylinder is shown in the figure above. The starting point is circular, the ending point is circular, and the cross-section is rectangular. Only in this way can one texture map a cylinder, which will be realized in the next article.
      2. Why is it so troublesome to calculate any two points in the space to generate a cylinder, instead of generating a circle in the modeling tool and then extruding a cylinder? Or generate a cylinder on the normalized xyz axis, and then translate it to the specified position by rotation?
      The above two methods are indeed easier to create a cylinder, but they do not meet the needs of the recent function. Recently, a function of a dynamic flexible body cable needs to be mathematicized in three-dimensional space to n-section rigid body bones, and then generate a cylindrical mesh skin through the two endpoints of these rigid body bones, and then generate a flexible cable through the mathematical model of the mass spring. Therefore, the goal is to generate a cylinder only through two vertices in space.
      Alright, that's all for today, we'll talk later.

Guess you like

Origin blog.csdn.net/yinhun2012/article/details/121796633