Unity绘制电线(三维空间两点生成曲线)

18 篇文章 5 订阅

绘制电线,分解出来就一下几个问题需解决:

  • 曲线生成的问题
  • 如何根据空间中两点确定一条曲线
  • 绘制的电线可以用鼠标点击

最终实现的效果如下:
最终效果
现在我们来一一解决。

贝塞尔曲线

关于贝塞尔曲线的原理,讲解的文章很多,大多是列出公式,然后就提供代码,其实讲得大同小异,公式怎么来的也没整明白。如果要了解贝塞尔曲线的原理,推荐这篇文章贝塞尔曲线,讲得很透彻。

以下是根据3点生成曲线的代码。

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

public class BezierUtils
{
/// <summary>
/// 获取存储贝塞尔曲线点的数组
/// </summary>
/// <param name="startPoint"></param>起始点
/// <param name="controlPoint"></param>控制点
/// <param name="endPoint"></param>目标点
/// <param name="segmentNum"></param>采样点的数量
/// <returns></returns>存储贝塞尔曲线点的数组
public static Vector3[] GetBeizerList(Vector3 startPoint, Vector3 controlPoint, Vector3 endPoint, int segmentNum)
{
    Vector3[] path = new Vector3[segmentNum+1];
    path[0] = startPoint;
    for (int i = 1; i <= segmentNum; i++)
    {
        float t = i / (float)segmentNum;
        Vector3 pixel = CalculateCubicBezierPoint(t, startPoint,
            controlPoint, endPoint);
        path[i] = pixel;
    }
    return path;
}

/// <summary>
/// 根据T值,计算贝塞尔曲线上面相对应的点
/// </summary>
/// <param name="t"></param>T值
/// <param name="p0"></param>起始点
/// <param name="p1"></param>控制点
/// <param name="p2"></param>目标点
/// <returns></returns>根据T值计算出来的贝赛尔曲线点
private static Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2)
{
    float u = 1 - t;
    float tt = t * t;
    float uu = u * u;

    Vector3 p = uu * p0;
    p += 2 * u * t * p1;
    p += tt * p2;

    return p;
}
}

两点确定一条曲线

首先要明确两点是确定不了一条曲线的!至少要3个点。你看贝塞尔曲线的脚本也有一个控制点,所以现在问题转换为如何通过两点找第三个控制点的问题。
第三个点如何找呢?最简单的就是找两点连成线的中垂线,但是空间中一条线段的中垂线有无数条啊,你总得告诉我中垂线指向哪儿或者说在哪个平面上才能确定啊。对了,在哪个平面上?我们已经知道两条线段可以确定一个平面,且目前我们知道一条线段的两个点了,那再指定一个方向不就可以得出平面了吗。
所以现在你要做的是人为指定一个方向
所以,如何求出中垂线呢?
在此之前需要理解一点:两个向量的叉乘得到的向量(法线)是垂直于两个向量所在平面的,即也垂直于这两个向量
步骤如下:

  • 人为确定一个方向,如Vector3.up,然后叉乘得出该平面的法线
  • 法线再与线段叉乘得出该线段的垂线
  • 然后再算出线段的中点
    垂线知道,中点知道,中垂线不就出来了吗,哈哈哈哈。
/// <summary>
/// 获取控制点.
/// </summary>
/// <param name="startPos">起点.</param>
/// <param name="endPos">终点.</param>
/// <param name="offset">偏移量.</param>
private Vector3 CalcControlPos(Vector3 startPos, Vector3 endPos, float offset)
{
    //方向(由起始点指向终点)
    Vector3 dir = endPos - startPos;
    //取另外一个方向. 这里取向上.
    Vector3 otherDir = Vector3.up;

    //求平面法线.  注意otherDir与dir不能调换位置,平面的法线是有方向的,(调换位置会导致法线方向相反)
    //ps: 左手坐标系使用左手定则 右手坐标系使用右手定则 (具体什么是左右手坐标系这里不细说请Google)
    //unity中世界坐标使用的是左手坐标系,所以法线的方向应该用左手定则判断.
    Vector3 planeNormal = Vector3.Cross(otherDir, dir);

    //再求startPos与endPos的垂线. 其实就是再求一次叉乘.
    Vector3 vertical = Vector3.Cross(dir, planeNormal).normalized;
    //中点.
    Vector3 centerPos = (startPos + endPos) / 2f;
    //控制点.
    Vector3 controlPos = centerPos + vertical * offset;

    return controlPos;
}

可以在Scene窗口实时查看中垂线的方向,以便确认计算是否正确,代码如下

private void OnDrawGizmos()
{
    if(endPos == null)
    {
        Awake();
    }
    //方向(由起始点指向终点)
    Vector3 dir = endPos.position - startPos.position;
    //取另外一个方向. 这里取向上.
    Vector3 otherDir = Vector3.up;

    //求平面法线.  注意otherDir与dir不能调换位置,平面的法线是有方向的,(调换位置会导致法线方向相反)
    //ps: 左手坐标系使用左手定则 右手坐标系使用右手定则 (具体什么是左右手坐标系这里不细说请Google)
    //unity中世界坐标使用的是左手坐标系,所以法线的方向应该用左手定则判断.
    Vector3 planeNormal = Vector3.Cross(otherDir, dir);

    //再求startPos与endPos的垂线. 其实就是再求一次叉乘.
    Vector3 vertical = Vector3.Cross(dir, planeNormal).normalized;
    //中点.
    Vector3 centerPos = (startPos.position + endPos.position) / 2f;
    //控制点.
    Vector3 controlPos = centerPos + vertical * offset;

    //线段.
    Gizmos.color = Color.white;
    Gizmos.DrawLine(startPos.position, endPos.position);

    //平面法线.
    Gizmos.color = Color.red;
    Gizmos.DrawLine(centerPos, controlPos + planeNormal.normalized * 5);

    //人为确定的方向.
    Gizmos.color = Color.blue;
    Gizmos.DrawLine(startPos.position, startPos.position + otherDir.normalized * 5);

    //中垂线.
    Gizmos.color = Color.green;
    Gizmos.DrawLine(centerPos, centerPos + vertical.normalized * 5);
}

给电线添加物理特性

两点生成曲线已经搞定啦,现在就差把线绘制出来并能交互。
把线绘制出来其实很简单,Unity自带的LineRendere已经能满足我们的要求。但是如何才能让LineRenderer可交互呢?添加碰撞器嘛!
有了碰撞器你想怎么个交互都行嘛。
添加碰撞器代码很简单,这个思路最重要。代码里面添加的是胶囊体碰撞器,如下:

/// <summary>
/// 给线添加碰撞体.
/// </summary>
/// <param name="poses">点集合.</param>
/// <param name="colls">碰撞器的父物体.</param>
/// <param name="radius">半径.</param>
private void AttackCollider(Vector3[] poses, Transform colls, float radius)
    {
        Vector3 lastPos = poses[0];
        for (int i = 1; i < poses.Length; i++)
        {
            Vector3 nextPos = poses[i];

            GameObject colliderObj = null;
            if (i <= colliders.Count-1)
            {
                colliderObj = colliders[i - 1];
            }
            else
            {
                colliderObj = new GameObject();
                colliders.Add(colliderObj);
            }
            
            colliderObj.name = (i - 1).ToString();
            colliderObj.transform.parent = colls;
            colliderObj.transform.forward = (nextPos - lastPos).normalized;

            CapsuleCollider coll = colliderObj.GetComponent<CapsuleCollider>();
            if(coll == null)
            {
                coll = colliderObj.AddComponent<CapsuleCollider>();
            }
            Vector3 center = (lastPos + nextPos) / 2f;

            //设置胶囊体参数.
            colliderObj.transform.position = center;
            coll.center = Vector3.zero;
            coll.radius = radius;
            coll.height = Vector3.Distance(lastPos, nextPos);
            coll.direction = 2;                         //0-X 1-Y 2-Z
            coll.tag = "Wire";

            lastPos = nextPos;
        }
    }

项目上传到GitHub了可下载查看。
下次再会!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值