在《遊戲程式設計精粹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);
}
}