辅助工具:圆柱体绘制

      功能下班写完了,冲一篇,记录一下,后续还有深入研究。
      假设三维空间中存在两个点,以这两个点为端点生成圆柱体,如下:
在这里插入图片描述
      空间中两端点f和t构成圆柱体,那么我们首先我们必须得到f和t所在的平面,然后求出平面上所有圆形网格顶点,这个上一篇空间圆已经详细讲解了,这里我们直接写代码计算所有顶点坐标,如下:

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;
    }
}

      效果如下:
在这里插入图片描述
      网线渲染绘制正确。
      接下来我们通过meshfilter来构建网格,如下:
在这里插入图片描述
      可以看出cylinder的triangles拓扑结构,当然这里为了图示,就用四个顶点的”圆形“,开始写代码:

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;
    }

      代码中需要注意的就是拓扑三角索引的构建,效果如下:
在这里插入图片描述

      可以看得出来网格构建ok,但是光照有问题,因为法向量缺失,我们还得构建法向量,如下:
在这里插入图片描述
      图示应该很容易理解,接下来写代码:

//构建法向量
        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);

      接下来见证一个神奇的时刻:
在这里插入图片描述      哈哈哈哈,可以看得出来圆柱体截面渲染正常,两端渲染好像一个半球形,这是当然的!因为两端圆形平面的法向量是通过平行平面F的nfarr和垂直平面F的nf线性插值得到的,这样就变成了半球,实际上unity圆柱体是由两端圆形加中间截面矩形组成,也就是一个圆柱体由三个网格面组成,这样的话才能避免两端平面圆形渲染异常的问题,如下:
在这里插入图片描述
      看得出来unity自带的cylinder 88verts 80tris,也就是说两端平面圆形和中间截面矩形是“断开”的,所以得重新处理mesh,如下:
在这里插入图片描述      接下来继续修改:

    /// <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;
    }

      解决了法向量光照的问题:
在这里插入图片描述
      到这里了,大致已经绘制出一个圆柱体,不过还是有问题需要说明和解决的:
      1.uv参数怎么计算?
在这里插入图片描述
      圆柱体的uv映射如上图所示,起点圆形和终点圆形还有截面矩形,这样才能一张纹理映射一个圆柱体,下一篇会实现。
      2.为什么要这么麻烦算空间任意两点生成圆柱体,而不是学建模工具中生成一个圆形,再挤出一个圆柱体?或者在规范化的xyz轴向生成圆柱体,再通过旋转平移到指定的位置?
      上面两种方式确实更简单的创建圆柱体,但是不满足最近做的功能的需求,最近做一个动态柔体电缆的功能,需要在三维空间中数学化n截刚体骨骼,然后通过这些刚体骨骼的两个端点生成圆柱体网格蒙皮,再通过质点弹簧相关数学模型,生成柔性电缆,所以目标就是只通过空间两顶点生成个圆柱体。
      好,今天就到这里,后面再聊。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值