游戏编程精粹学习 - 计算到区域内部的距离
在《游戏编程精粹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; } } }
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); } }