游戏编程精粹学习 - 计算到区域内部的距离

在《游戏编程精粹1》的4.7中,原文主要解决赛车游戏的路程确定问题和光照插值问题。

但原文中没有提及如何判断四边形区域是否包含的问题,只有提到point-in-sector这个函数名称

实现是T和L两部分做向量投影,但是不乘以最终方向矢量,而是以两边的点乘结果求得比例。

 

那么我对其做了一些修改,改成了横向和纵向二维的单位距离计算,这样可以顺手解决是否包含的判断问题。

 

 

 

支持两个轴向之后可以对其做是否包含的判断,还可以通过组合做一些复杂区域的判断检测。

例如运用在游戏BOSS战,或RailCamera中。

 

 

代码如下:

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

namespace Hont
{
    public class QuadSector : MonoBehaviour
    {
        public struct QuadSector_VecXZ
        {
            public float X { get; set; }
            public float Z { get; set; }


            public QuadSector_VecXZ(float x, float z)
            {
                X = x;
                Z = z;
            }
        }

        public Transform p0;
        public Transform p1;
        public Transform p2;
        public Transform p3;
        public bool isRealtimeUpdate;

        QuadSector_VecXZ mHPointLeading;
        QuadSector_VecXZ mHPointTrailing;
        QuadSector_VecXZ mHUnitNormalLeading;
        QuadSector_VecXZ mHUnitNormalTrailing;

        QuadSector_VecXZ mVPointLeading;
        QuadSector_VecXZ mVPointTrailing;
        QuadSector_VecXZ mVUnitNormalLeading;
        QuadSector_VecXZ mVUnitNormalTrailing;

        public QuadSector_VecXZ HPointLeading { get { return mHPointLeading; } set { mHPointLeading = value; } }
        public QuadSector_VecXZ HPointTrailing { get { return mHPointTrailing; } set { mHPointTrailing = value; } }
        public QuadSector_VecXZ HUnitNormalLeading { get { return mHUnitNormalLeading; } set { mHUnitNormalLeading = value; } }
        public QuadSector_VecXZ HUnitNormalTrailing { get { return mHUnitNormalTrailing; } set { mHUnitNormalTrailing = value; } }

        public QuadSector_VecXZ VPointLeading { get { return mVPointLeading; } set { mVPointLeading = value; } }
        public QuadSector_VecXZ VPointTrailing { get { return mVPointTrailing; } set { mVPointTrailing = value; } }
        public QuadSector_VecXZ VUnitNormalLeading { get { return mVUnitNormalLeading; } set { mVUnitNormalLeading = value; } }
        public QuadSector_VecXZ VUnitNormalTrailing { get { return mVUnitNormalTrailing; } set { mVUnitNormalTrailing = value; } }


        public void UpdateHorizontalQuadSectorInfo()
        {
            var pl = (p1.position + p2.position) * 0.5f;
            mHPointLeading = new QuadSector_VecXZ(pl.x, pl.z);

            var pt = (p0.position + p3.position) * 0.5f;
            mHPointTrailing = new QuadSector_VecXZ(pt.x, pt.z);

            var nl = CalcLineNormal(p1.position, p2.position, Vector3.up, p3.position);
            mHUnitNormalLeading = new QuadSector_VecXZ(nl.x, nl.z);

            var nt = CalcLineNormal(p0.position, p3.position, Vector3.up, p1.position);
            mHUnitNormalTrailing = new QuadSector_VecXZ(nt.x, nt.z);
        }

        public void UpdateVerticalQuadSectorInfo()
        {
            var pl = (p1.position + p0.position) * 0.5f;
            mVPointLeading = new QuadSector_VecXZ(pl.x, pl.z);

            var pt = (p2.position + p3.position) * 0.5f;
            mVPointTrailing = new QuadSector_VecXZ(pt.x, pt.z);

            var nl = CalcLineNormal(p1.position, p0.position, Vector3.up, p3.position);
            mVUnitNormalLeading = new QuadSector_VecXZ(nl.x, nl.z);

            var nt = CalcLineNormal(p2.position, p3.position, Vector3.up, p0.position);
            mVUnitNormalTrailing = new QuadSector_VecXZ(nt.x, nt.z);
        }

        public float CalcHorizontalUnitDistanceIntoSector(float pointX, float pointZ)
        {
            var lp = new QuadSector_VecXZ();
            var tp = new QuadSector_VecXZ();
            var dotL = 0f;
            var dotT = 0f;

            lp.X = pointX - HPointLeading.X;
            lp.Z = pointZ - HPointLeading.Z;

            tp.X = pointX - HPointTrailing.X;
            tp.Z = pointZ - HPointTrailing.Z;

            dotL = lp.X * mHUnitNormalLeading.X + lp.Z * mHUnitNormalLeading.Z;
            dotT = tp.X * mHUnitNormalTrailing.X + tp.Z * mHUnitNormalTrailing.Z;

            return dotL / (dotL + dotT);
        }

        public float CalcVerticalUnitDistanceIntoSector(float pointX, float pointZ)
        {
            var lp = new QuadSector_VecXZ();
            var tp = new QuadSector_VecXZ();
            var dotL = 0f;
            var dotT = 0f;

            lp.X = pointX - VPointLeading.X;
            lp.Z = pointZ - VPointLeading.Z;

            tp.X = pointX - VPointTrailing.X;
            tp.Z = pointZ - VPointTrailing.Z;

            dotL = lp.X * mVUnitNormalLeading.X + lp.Z * mVUnitNormalLeading.Z;
            dotT = tp.X * mVUnitNormalTrailing.X + tp.Z * mVUnitNormalTrailing.Z;

            return dotL / (dotL + dotT);
        }

        public bool IsContain(float pointX, float pointZ)
        {
            var x = CalcHorizontalUnitDistanceIntoSector(transform.position.x, transform.position.z);
            var z = CalcVerticalUnitDistanceIntoSector(transform.position.x, transform.position.z);

            return x > 0 && x < 1 && z > 0 && z < 1;
        }

        Vector3 CalcLineNormal(Vector3 p0, Vector3 p1, Vector3 upAxis, Vector3 comparePoint)
        {
            var dir = (p1 - p0).normalized;
            var normal1 = Vector3.Cross(dir, upAxis);
            var normal2 = Vector3.Cross(dir, -upAxis);

            if (Vector3.Dot(normal1, comparePoint - p0) > 0)
                return normal1;
            else
                return normal2;
        }

        void Awake()
        {
            UpdateHorizontalQuadSectorInfo();
            UpdateVerticalQuadSectorInfo();
        }

        void Update()
        {
            if (isRealtimeUpdate)
            {
                UpdateHorizontalQuadSectorInfo();
                UpdateVerticalQuadSectorInfo();
            }
        }

        void OnDrawGizmos()
        {
            if (p0 == null || p1 == null || p2 == null || p3 == null) return;

            if (!Application.isPlaying)
            {
                UpdateHorizontalQuadSectorInfo();
                UpdateVerticalQuadSectorInfo();
            }

            Gizmos.DrawLine(p0.position, p1.position);
            Gizmos.DrawLine(p1.position, p2.position);
            Gizmos.DrawLine(p2.position, p3.position);
            Gizmos.DrawLine(p3.position, p0.position);

            var cacheColor = Gizmos.color;
            Gizmos.color = Color.blue;

            var ori = new Vector3(HPointLeading.X, transform.position.y, HPointLeading.Z);
            Gizmos.DrawLine(ori, ori + new Vector3(HUnitNormalLeading.X, transform.position.y, HUnitNormalLeading.Z));

            ori = new Vector3(VPointLeading.X, transform.position.y, VPointLeading.Z);
            Gizmos.DrawLine(ori, ori + new Vector3(VUnitNormalLeading.X, transform.position.y, VUnitNormalLeading.Z));

            ori = new Vector3(HPointTrailing.X, transform.position.y, HPointTrailing.Z);
            Gizmos.DrawLine(ori, ori + new Vector3(HUnitNormalTrailing.X, transform.position.y, HUnitNormalTrailing.Z));

            ori = new Vector3(VPointTrailing.X, transform.position.y, VPointTrailing.Z);
            Gizmos.DrawLine(ori, ori + new Vector3(VUnitNormalTrailing.X, transform.position.y, VUnitNormalTrailing.Z));

            Gizmos.color = cacheColor;
        }
    }
}
QuadSector

 

 

P0-P3四个点沿顺时针方向排布,如果不勾选实时更新则法线和顶点都不会在运行时修改。

 

测试脚本:

public class TestPlayer : MonoBehaviour
{
    public QuadSector quadSector;


    void Update()
    {
        var x = quadSector.CalcHorizontalUnitDistanceIntoSector(transform.position.x, transform.position.z);
        var z = quadSector.CalcVerticalUnitDistanceIntoSector(transform.position.x, transform.position.z);
        Debug.Log("x: " + x + " z: " + z);
    }
}

 

posted @ 2018-04-15 16:24  HONT  阅读(433)  评论(0编辑  收藏  举报