使用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.其它需要注意的一些设置

posted @ 2018-02-26 15:52  Marsir  阅读(3321)  评论(0编辑  收藏  举报