使用Unity實現VR中在黑板上寫字(升級篇)(一)-----解決畫筆穿透畫板的問題


一、概述:

使用Unity實現VR中在黑板上寫字(初級篇)中的最后留下了一些有待完善的地方,首先完善畫筆穿透畫板的問題;

在之前使用畫筆會出現這種情況:


可以看到畫筆是穿透了畫板,這樣在VR中會給用戶很差的體驗,而且因為代碼的原因會造成畫的過程中中斷,所以這個問題必須解決;

解決后的使用情況:

 

可以看到現在不會穿透了,而且畫起來不會有中斷,其實我的手的位置已經穿到畫板后面了;

實現這個功能,其實有很多種方法,但是最終覺得參照The Lab里實現的方法比較好————用一個Plane類型解決;

二、知識點

Plane這個類中有兩個方法:

1.public bool GetSide(Vector3 inPt);判斷一個點在Plane的哪一側

  Plane把一個空間分成了兩部分,當一個點在Plane的normal(法線)指向的一側的時候,這個值返回True,否則返回False;

2.public float GetDistanceToPoint(Vector3 inPt);判斷一個點距離平面的距離,這個值是帶符號的,當點在平面的正面的時候,返回正值,否則返回負值;

利用這兩個方法可以判斷出筆尖是不是穿透了畫板;

三、代碼原理

筆尖穿透了多少距離,我們就補多少回去,以俯視的視角,想像這個情況

 

 

根據上圖,可以等到筆尖穿透的距離,從筆尖的位置減去這個距離,則剛好可以讓筆尖處於平面上;

因為用的是VRTK插件,這個插件集成了很好的物理交互功能,比如抓起東西;VRTK中寫了不少抓起物體的機制:

1.VRTK_ChildOfControllerGrabAttach

2.VRTK_ClimbableGrabAttach

3.VRTK_CustomJointGrabAttach

4.VRTK_FixedJointGrabAttach

5.VRTK_RotatorTrackGrabAttach

6.VRTK_SpringJointGrabAttach

7.VRTK_TrackObjectGrabAttach

對於穿透這種情況,沒有一種是比較合適的,因此需要自己擴展一種抓附機制;上面的抓附機制都是直接或者間接繼承自VRTK_BaseGrabAttach的,因為定義一個VRTK_InteractableObject的抓起方式的字段就是VRTK_BaseGrabAttach類型;

所以綜上,需要擴展一個VRTK_BaseGrabAttach類型的抓附機制,並且Board類有了一些改變,需要增加一個UnityEngine.Plane類型的字段,以及封裝一些函數;

四、代碼實現

首先Board類增加一個Plane類型的字段

internal Plane boardPlane;

然后初始化這個字段

   private void Start()
    {
        //初始化Plane,讓它的法線是這個畫板的forward向量,並且法線通過畫板的中心位置,由此確定一個平面
        boardPlane = new Plane(transform.forward, transform.position);
     ......
}

最后增加核心的方法

/// <summary>
    /// 判斷筆尖是在畫板的正面還是背面
    /// </summary>
    /// <param name="point">筆尖的位置</param>
    /// <returns>true 在正面;false 在背面</returns>
    public bool GetSideOfBoardPlane(Vector3 point)
    {
        return boardPlane.GetSide(point);
    }

    /// <summary>
    /// 筆尖與平面的距離
    /// </summary>
    /// <param name="point">筆尖的位置</param>
    /// <returns>當在正面的時候返回正值,當在背面的時候返回負值</returns>
    public float GetDistanceFromBoardPlane(Vector3 point)
    {
        return boardPlane.GetDistanceToPoint(point);
    }

    /// <summary>
    /// 矯正后的筆尖應該在的位置
    /// </summary>
    /// <param name="point">筆尖的位置</param>
    /// <returns>矯正后的筆尖位置</returns>
    public Vector3 ProjectPointOnBoardPlane(Vector3 point)
    {
        float d = -Vector3.Dot(boardPlane.normal, point - transform.position);
        return point + boardPlane.normal * d;
    }

然后擴展一個抓附機制

using VRTK.GrabAttachMechanics;
using UnityEngine;

public class PainterGrabAttach : VRTK_BaseGrabAttach
{

    [Header("Painter Options")]

    [SerializeField]
    private Transform tips;//筆尖
    private static Board board;//畫板

    #region 重寫的父類方法
    protected override void Initialise()
    {
        //初始化父類的一些字段,這些字段只是標識這個抓附機制的作用
        tracked = false;
        kinematic = false;
        climbable = false;

        //初始化自定義的屬性
        if (precisionGrab)//最好不要用精確抓取,因為這樣很有可能會讓筆處於一個不合理的位置,這樣使用的時候,會很變扭(比如必須手腕旋轉一個角度,筆才是正的) 
        {
            Debug.LogError("PrecisionGrab cant't be true in case of PainterGrabAttach Mechanic");
        }
        board = FindObjectOfType<Board>();
    }

    public override bool StartGrab(GameObject grabbingObject, GameObject givenGrabbedObject, Rigidbody givenControllerAttachPoint)
    {
        if (base.StartGrab(grabbingObject, givenGrabbedObject, givenControllerAttachPoint))
        {
            SnapObjectToGrabToController(givenGrabbedObject);
            grabbedObjectScript.IsKinematic = true;
            return true;
        }
        return false;
    }

    public override void StopGrab(bool applyGrabbingObjectVelocity)
    {
        ReleaseObject(applyGrabbingObjectVelocity);
        base.StopGrab(applyGrabbingObjectVelocity);
    }

    public override void ProcessFixedUpdate()
    {
        if (grabbedObject)//只有抓住物體后,grabbedObject才不會
        {
            grabbedObject.transform.rotation = controllerAttachPoint.transform.rotation * Quaternion.Euler(grabbedSnapHandle.transform.localEulerAngles);
            grabbedObject.transform.position = controllerAttachPoint.transform.position - (grabbedSnapHandle.transform.position - grabbedObject.transform.position);
            float distance = board.GetDistanceFromBoardPlane(tips.position);//筆尖距離平面的距離
            bool isPositiveOfBoardPlane = board.GetSideOfBoardPlane(tips.position);//筆尖是不是在筆尖的正面
            Vector3 direction = grabbedObject.transform.position - tips.position;//筆尖位置指向筆的位置的差向量
            //當筆尖穿透的時候,需要矯正筆的位置 
            if (isPositiveOfBoardPlane || distance > 0.0001f)
            {
                Vector3 pos = board.ProjectPointOnBoardPlane(tips.position);
                grabbedObject.transform.position = pos - board.boardPlane.normal * 0.001f + direction;//pos是筆尖的位置,而不是筆的位置,加上direction后才是筆的位置 
            }
        }
    }

    #endregion

    //讓手柄抓住物體
    private void SnapObjectToGrabToController(GameObject obj)
    {
        if (!precisionGrab)
        {
            SetSnappedObjectPosition(obj);
        }
    }

    //設置物體和手柄連接的位置 
    private void SetSnappedObjectPosition(GameObject obj)
    {
        if (grabbedSnapHandle == null)
        {
            obj.transform.position = controllerAttachPoint.transform.position;
        }
        else
        {
            //設置旋轉,controllerAttachPoint是手柄上的一個與物體的連接點
            obj.transform.rotation = controllerAttachPoint.transform.rotation * Quaternion.Euler(grabbedSnapHandle.transform.localEulerAngles);
            //因為grabbedSnapHandle和obj.transform之間可能不是同一個點,所以為了讓手柄抓的位置是grabbedSnapHandle,需要減去括號中代表的向量
            obj.transform.position = controllerAttachPoint.transform.position - (grabbedSnapHandle.transform.position - obj.transform.position);
        }
    }

}

五、場景中的設置

1.筆尖的位置還是要設置好

筆尖的位置要設置在筆芯的尖端,snapPoint的位置和旋轉,決定了手柄抓住筆時的位置和旋轉;

2.其它需要注意的一些設置


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM