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

  • 相关阅读:
    3. VIM 系列
    2. VIM 系列
    2. 修复FFMPEG 复用 PAT、PMT发送间隔小于25ms的错误
    1. VIM 系列
    RESTful风格API
    APIview的使用
    1.DRF初始化
    Linux下的django项目02
    Linux下的django项目01
    1,web项目工作流程
  • 原文地址:https://www.cnblogs.com/marsir/p/8473635.html
Copyright © 2020-2023  润新知