Files
XRLib/Assets/Runtime Transform Gizmos/Scripts/Runtime Package Common/Math/Primitive Math/BoxMath.cs
2025-12-12 18:58:44 +09:00

587 lines
22 KiB
C#

using UnityEngine;
using System.Collections.Generic;
namespace RTG
{
public enum BoxCorner
{
FrontTopLeft = 0,
FrontTopRight,
FrontBottomRight,
FrontBottomLeft,
BackTopLeft,
BackTopRight,
BackBottomRight,
BackBottomLeft
}
public enum BoxFace
{
Front = 0,
Back,
Left,
Right,
Bottom,
Top
}
public enum BoxFaceAreaType
{
Invalid = 0,
Quad,
Line
}
public struct BoxFaceAreaDesc
{
public BoxFaceAreaType AreaType;
public float Area;
public BoxFaceAreaDesc(BoxFaceAreaType areaType, float area)
{
AreaType = areaType;
Area = area;
}
public static BoxFaceAreaDesc GetInvalid()
{
return new BoxFaceAreaDesc(BoxFaceAreaType.Invalid, 0.0f);
}
}
public struct BoxFaceDesc
{
public BoxFace Face;
public Plane Plane;
public Vector3 Center;
}
public static class BoxMath
{
private static List<BoxFace> _allBoxFaces = new List<BoxFace>();
static BoxMath()
{
_allBoxFaces.Add(BoxFace.Front);
_allBoxFaces.Add(BoxFace.Back);
_allBoxFaces.Add(BoxFace.Left);
_allBoxFaces.Add(BoxFace.Right);
_allBoxFaces.Add(BoxFace.Bottom);
_allBoxFaces.Add(BoxFace.Top);
}
public static List<BoxFace> AllBoxFaces { get { return new List<BoxFace>(_allBoxFaces); } }
public static int GetFaceAxisIndex(BoxFace face)
{
if (face == BoxFace.Top || face == BoxFace.Bottom) return 1;
if (face == BoxFace.Left || face == BoxFace.Right) return 0;
if (face == BoxFace.Back || face == BoxFace.Front) return 2;
return -1;
}
public static BoxFaceDesc GetFaceClosestToPoint(Vector3 point, Vector3 boxCenter, Vector3 boxSize, Quaternion boxRotation)
{
float minDist = float.MaxValue;
Plane bestPlane = new Plane();
BoxFace bestFace = BoxFace.Front;
var allFaces = AllBoxFaces;
foreach(var face in allFaces)
{
Plane facePlane = BoxMath.CalcBoxFacePlane(boxCenter, boxSize, boxRotation, face);
float dist = facePlane.GetAbsDistanceToPoint(point);
if(dist < minDist)
{
bestFace = face;
bestPlane = facePlane;
minDist = dist;
}
}
return new BoxFaceDesc()
{
Face = bestFace,
Plane = bestPlane,
Center = BoxMath.CalcBoxFaceCenter(boxCenter, boxSize, boxRotation, bestFace)
};
}
public static BoxFaceDesc GetFaceClosestToPoint(Vector3 point, Vector3 boxCenter, Vector3 boxSize, Quaternion boxRotation, Vector3 viewVector)
{
float minDist = float.MaxValue;
Plane bestPlane = new Plane();
BoxFace bestFace = BoxFace.Front;
var allFaces = AllBoxFaces;
foreach (var face in allFaces)
{
Plane facePlane = BoxMath.CalcBoxFacePlane(boxCenter, boxSize, boxRotation, face);
float dotView = Vector3.Dot(viewVector, facePlane.normal);
if (dotView >= 0.0f) continue;
float dist = facePlane.GetAbsDistanceToPoint(point);
if (dist < minDist)
{
bestFace = face;
bestPlane = facePlane;
minDist = dist;
}
}
return new BoxFaceDesc()
{
Face = bestFace,
Plane = bestPlane,
Center = BoxMath.CalcBoxFaceCenter(boxCenter, boxSize, boxRotation, bestFace)
};
}
public static BoxFace GetMostAlignedFace(Vector3 boxCenter, Vector3 boxSize, Quaternion boxRotation, Vector3 direction)
{
var allFaces = AllBoxFaces;
int bestFaceIndex = 0;
float bestScore = Vector3.Dot(direction, CalcBoxFaceNormal(boxCenter, boxSize, boxRotation, allFaces[0]));
for (int faceIndex = 1; faceIndex < allFaces.Count; ++faceIndex)
{
float score = Vector3.Dot(direction, CalcBoxFaceNormal(boxCenter, boxSize, boxRotation, allFaces[faceIndex]));
if (score > bestScore)
{
bestScore = score;
bestFaceIndex = faceIndex;
}
}
return allFaces[bestFaceIndex];
}
public static Vector3 CalcBoxFaceSize(Vector3 boxSize, BoxFace boxFace)
{
Vector3 faceSize = boxSize;
if (boxFace == BoxFace.Front || boxFace == BoxFace.Back) faceSize.z = 0.0f;
else if (boxFace == BoxFace.Left || boxFace == BoxFace.Right) faceSize.x = 0.0f;
else faceSize.y = 0.0f;
return faceSize;
}
public static BoxFaceAreaDesc GetBoxFaceAreaDesc(Vector3 boxSize, BoxFace boxFace)
{
if (boxFace == BoxFace.Front || boxFace == BoxFace.Back)
{
float area = boxSize.x * boxSize.y;
if (area < 1e-6f) return new BoxFaceAreaDesc(BoxFaceAreaType.Line, Mathf.Max(boxSize.x, boxSize.y));
return new BoxFaceAreaDesc(BoxFaceAreaType.Quad, area);
}
else if (boxFace == BoxFace.Left || boxFace == BoxFace.Right)
{
float area = boxSize.y * boxSize.z;
if (area < 1e-6f) return new BoxFaceAreaDesc(BoxFaceAreaType.Line, Mathf.Max(boxSize.y, boxSize.z));
return new BoxFaceAreaDesc(BoxFaceAreaType.Quad, area);
}
else
{
float area = boxSize.x * boxSize.z;
if (area < 1e-6f) return new BoxFaceAreaDesc(BoxFaceAreaType.Line, Mathf.Max(boxSize.x, boxSize.z));
return new BoxFaceAreaDesc(BoxFaceAreaType.Quad, area);
}
}
public static Plane CalcBoxFacePlane(Vector3 boxCenter, Vector3 boxSize, Quaternion boxRotation, BoxFace boxFace)
{
Vector3 extents = boxSize * 0.5f;
Vector3 rightAxis = boxRotation * Vector3.right;
Vector3 upAxis = boxRotation * Vector3.up;
Vector3 lookAxis = boxRotation * Vector3.forward;
switch (boxFace)
{
case BoxFace.Front:
return new Plane(-lookAxis, boxCenter - lookAxis * extents.z);
case BoxFace.Back:
return new Plane(lookAxis, boxCenter + lookAxis * extents.z);
case BoxFace.Left:
return new Plane(-rightAxis, boxCenter - rightAxis * extents.x);
case BoxFace.Right:
return new Plane(rightAxis, boxCenter + rightAxis * extents.x);
case BoxFace.Bottom:
return new Plane(-upAxis, boxCenter - upAxis * extents.y);
default:
return new Plane(upAxis, boxCenter + upAxis * extents.y);
}
}
public static Vector3 CalcBoxFaceNormal(Vector3 boxCenter, Vector3 boxSize, Quaternion boxRotation, BoxFace boxFace)
{
Vector3 rightAxis = boxRotation * Vector3.right;
Vector3 upAxis = boxRotation * Vector3.up;
Vector3 lookAxis = boxRotation * Vector3.forward;
switch (boxFace)
{
case BoxFace.Front:
return -lookAxis;
case BoxFace.Back:
return lookAxis;
case BoxFace.Left:
return -rightAxis;
case BoxFace.Right:
return rightAxis;
case BoxFace.Bottom:
return -upAxis;
default:
return upAxis;
}
}
public static Vector3 CalcBoxFaceCenter(Vector3 boxCenter, Vector3 boxSize, Quaternion boxRotation, BoxFace boxFace)
{
Vector3 extents = boxSize * 0.5f;
Vector3 rightAxis = boxRotation * Vector3.right;
Vector3 upAxis = boxRotation * Vector3.up;
Vector3 lookAxis = boxRotation * Vector3.forward;
switch(boxFace)
{
case BoxFace.Front:
return boxCenter - lookAxis * extents.z;
case BoxFace.Back:
return boxCenter + lookAxis * extents.z;
case BoxFace.Left:
return boxCenter - rightAxis * extents.x;
case BoxFace.Right:
return boxCenter + rightAxis * extents.x;
case BoxFace.Bottom:
return boxCenter - upAxis * extents.y;
default:
return boxCenter + upAxis * extents.y;
}
}
public static List<Vector3> CalcBoxCornerPoints(Vector3 boxCenter, Vector3 boxSize, Quaternion boxRotation)
{
Vector3 extents = boxSize * 0.5f;
Vector3 rightAxis = boxRotation * Vector3.right;
Vector3 upAxis = boxRotation * Vector3.up;
Vector3 lookAxis = boxRotation * Vector3.forward;
var cornerPoints = new Vector3[8];
Vector3 faceCenter = boxCenter - lookAxis * extents.z;
cornerPoints[(int)BoxCorner.FrontTopLeft] = faceCenter - rightAxis * extents.x + upAxis * extents.y;
cornerPoints[(int)BoxCorner.FrontTopRight] = faceCenter + rightAxis * extents.x + upAxis * extents.y;
cornerPoints[(int)BoxCorner.FrontBottomRight] = faceCenter + rightAxis * extents.x - upAxis * extents.y;
cornerPoints[(int)BoxCorner.FrontBottomLeft] = faceCenter - rightAxis * extents.x - upAxis * extents.y;
faceCenter = boxCenter + lookAxis * extents.z;
cornerPoints[(int)BoxCorner.BackTopLeft] = faceCenter + rightAxis * extents.x + upAxis * extents.y;
cornerPoints[(int)BoxCorner.BackTopRight] = faceCenter - rightAxis * extents.x + upAxis * extents.y;
cornerPoints[(int)BoxCorner.BackBottomRight] = faceCenter - rightAxis * extents.x - upAxis * extents.y;
cornerPoints[(int)BoxCorner.BackBottomLeft] = faceCenter + rightAxis * extents.x - upAxis * extents.y;
return new List<Vector3>(cornerPoints);
}
public static void TransformBox(Vector3 boxCenter, Vector3 boxSize, Matrix4x4 transformMatrix, out Vector3 newBoxCenter, out Vector3 newBoxSize)
{
Vector3 rightAxis = transformMatrix.GetColumn(0);
Vector3 upAxis = transformMatrix.GetColumn(1);
Vector3 lookAxis = transformMatrix.GetColumn(2);
Vector3 extents = boxSize * 0.5f;
Vector3 newExtentsRight = rightAxis * extents.x;
Vector3 newExtentsUp = upAxis * extents.y;
Vector3 newExtentsLook = lookAxis * extents.z;
float newExtentX = Mathf.Abs(newExtentsRight.x) + Mathf.Abs(newExtentsUp.x) + Mathf.Abs(newExtentsLook.x);
float newExtentY = Mathf.Abs(newExtentsRight.y) + Mathf.Abs(newExtentsUp.y) + Mathf.Abs(newExtentsLook.y);
float newExtentZ = Mathf.Abs(newExtentsRight.z) + Mathf.Abs(newExtentsUp.z) + Mathf.Abs(newExtentsLook.z);
newBoxCenter = transformMatrix.MultiplyPoint(boxCenter);
newBoxSize = new Vector3(newExtentX, newExtentY, newExtentZ) * 2.0f;
}
#region BoxIntersectsBox Heap Alloc
private static Vector3[] A = new Vector3[3];
private static Vector3[] B = new Vector3[3];
private static float[,] R = new float[3, 3];
private static float[,] absR = new float[3, 3];
#endregion
public static bool BoxIntersectsBox(Vector3 center0, Vector3 size0, Quaternion rotation0, Vector3 center1, Vector3 size1, Quaternion rotation1)
{
A[0] = rotation0 * Vector3.right;
A[1] = rotation0 * Vector3.up;
A[2] = rotation0 * Vector3.forward;
B[0] = rotation1 * Vector3.right;
B[1] = rotation1 * Vector3.up;
B[2] = rotation1 * Vector3.forward;
// Note: We're using column major matrices.
for (int row = 0; row < 3; ++row)
{
for (int column = 0; column < 3; ++column)
{
R[row, column] = Vector3.Dot(A[row], B[column]);
}
}
Vector3 extents = size0 * 0.5f;
Vector3 AEx = new Vector3(extents.x, extents.y, extents.z);
extents = size1 * 0.5f;
Vector3 BEx = new Vector3(extents.x, extents.y, extents.z);
// Construct absolute rotation error matrix to account for cases when 2 local axes are parallel
const float epsilon = 1e-4f;
for (int row = 0; row < 3; ++row)
{
for (int column = 0; column < 3; ++column)
{
absR[row, column] = Mathf.Abs(R[row, column]) + epsilon;
}
}
Vector3 trVector = center1 - center0;
Vector3 t = new Vector3(Vector3.Dot(trVector, A[0]), Vector3.Dot(trVector, A[1]), Vector3.Dot(trVector, A[2]));
// Test extents projection on box A local axes (A0, A1, A2)
for (int axisIndex = 0; axisIndex < 3; ++axisIndex)
{
float bExtents = BEx[0] * absR[axisIndex, 0] + BEx[1] * absR[axisIndex, 1] + BEx[2] * absR[axisIndex, 2];
if (Mathf.Abs(t[axisIndex]) > AEx[axisIndex] + bExtents) return false;
}
// Test extents projection on box B local axes (B0, B1, B2)
for (int axisIndex = 0; axisIndex < 3; ++axisIndex)
{
float aExtents = AEx[0] * absR[0, axisIndex] + AEx[1] * absR[1, axisIndex] + AEx[2] * absR[2, axisIndex];
if (Mathf.Abs(t[0] * R[0, axisIndex] +
t[1] * R[1, axisIndex] +
t[2] * R[2, axisIndex]) > aExtents + BEx[axisIndex]) return false;
}
// Test axis A0 x B0
float ra = AEx[1] * absR[2, 0] + AEx[2] * absR[1, 0];
float rb = BEx[1] * absR[0, 2] + BEx[2] * absR[0, 1];
if (Mathf.Abs(t[2] * R[1, 0] - t[1] * R[2, 0]) > ra + rb) return false;
// Test axis A0 x B1
ra = AEx[1] * absR[2, 1] + AEx[2] * absR[1, 1];
rb = BEx[0] * absR[0, 2] + BEx[2] * absR[0, 0];
if (Mathf.Abs(t[2] * R[1, 1] - t[1] * R[2, 1]) > ra + rb) return false;
// Test axis A0 x B2
ra = AEx[1] * absR[2, 2] + AEx[2] * absR[1, 2];
rb = BEx[0] * absR[0, 1] + BEx[1] * absR[0, 0];
if (Mathf.Abs(t[2] * R[1, 2] - t[1] * R[2, 2]) > ra + rb) return false;
// Test axis A1 x B0
ra = AEx[0] * absR[2, 0] + AEx[2] * absR[0, 0];
rb = BEx[1] * absR[1, 2] + BEx[2] * absR[1, 1];
if (Mathf.Abs(t[0] * R[2, 0] - t[2] * R[0, 0]) > ra + rb) return false;
// Test axis A1 x B1
ra = AEx[0] * absR[2, 1] + AEx[2] * absR[0, 1];
rb = BEx[0] * absR[1, 2] + BEx[2] * absR[1, 0];
if (Mathf.Abs(t[0] * R[2, 1] - t[2] * R[0, 1]) > ra + rb) return false;
// Test axis A1 x B2
ra = AEx[0] * absR[2, 2] + AEx[2] * absR[0, 2];
rb = BEx[0] * absR[1, 1] + BEx[1] * absR[1, 0];
if (Mathf.Abs(t[0] * R[2, 2] - t[2] * R[0, 2]) > ra + rb) return false;
// Test axis A2 x B0
ra = AEx[0] * absR[1, 0] + AEx[1] * absR[0, 0];
rb = BEx[1] * absR[2, 2] + BEx[2] * absR[2, 1];
if (Mathf.Abs(t[1] * R[0, 0] - t[0] * R[1, 0]) > ra + rb) return false;
// Test axis A2 x B1
ra = AEx[0] * absR[1, 1] + AEx[1] * absR[0, 1];
rb = BEx[0] * absR[2, 2] + BEx[2] * absR[2, 0];
if (Mathf.Abs(t[1] * R[0, 1] - t[0] * R[1, 1]) > ra + rb) return false;
// Test axis A2 x B2
ra = AEx[0] * absR[1, 2] + AEx[1] * absR[0, 2];
rb = BEx[0] * absR[2, 1] + BEx[1] * absR[2, 0];
if (Mathf.Abs(t[1] * R[0, 2] - t[0] * R[1, 2]) > ra + rb) return false;
return true;
}
public static Vector3 CalcBoxPtClosestToPt(Vector3 point, Vector3 boxCenter, Vector3 boxSize, Quaternion boxRotation)
{
Vector3 fromCenterToPt = point - boxCenter;
Vector3[] localAxes = new Vector3[] { boxRotation * Vector3.right, boxRotation * Vector3.up, boxRotation * Vector3.forward };
Vector3 extents = boxSize * 0.5f;
Vector3 closestPt = boxCenter;
for (int axisIndex = 0; axisIndex < 3; ++axisIndex)
{
float projection = Vector3.Dot(localAxes[axisIndex], fromCenterToPt);
if (projection > extents[axisIndex]) projection = extents[axisIndex];
else if (projection < -extents[axisIndex]) projection = -extents[axisIndex];
closestPt += localAxes[axisIndex] * projection;
}
return closestPt;
}
public static bool Raycast(Ray ray, Vector3 boxCenter, Vector3 boxSize, Quaternion boxRotation, BoxEpsilon epsilon = new BoxEpsilon())
{
float t;
return Raycast(ray, out t, boxCenter, boxSize, boxRotation, epsilon);
}
public static bool Raycast(Ray ray, out float t, Vector3 boxCenter, Vector3 boxSize, Quaternion boxRotation, BoxEpsilon epsilon = new BoxEpsilon())
{
t = 0.0f;
boxSize += epsilon.SizeEps;
const float minBoxSize = 1e-6f;
int invalidSizeCounter = 0;
if (boxSize.x < minBoxSize) ++invalidSizeCounter;
if (boxSize.y < minBoxSize) ++invalidSizeCounter;
if (boxSize.z < minBoxSize) ++invalidSizeCounter;
if (invalidSizeCounter > 1) return false;
if (invalidSizeCounter == 1)
{
if (boxSize.x < minBoxSize)
{
Vector3 quadRight = boxRotation * Vector3.forward;
Vector3 quadUp = boxRotation * Vector3.up;
return QuadMath.Raycast(ray, out t, boxCenter, boxSize.z, boxSize.y, quadRight, quadUp);
}
else
if (boxSize.y < minBoxSize)
{
Vector3 quadRight = boxRotation * Vector3.right;
Vector3 quadUp = boxRotation * Vector3.forward;
return QuadMath.Raycast(ray, out t, boxCenter, boxSize.x, boxSize.z, quadRight, quadUp);
}
else
{
Vector3 quadRight = boxRotation * Vector3.right;
Vector3 quadUp = boxRotation * Vector3.up;
return QuadMath.Raycast(ray, out t, boxCenter, boxSize.x, boxSize.y, quadRight, quadUp);
}
}
Matrix4x4 boxMatrix = Matrix4x4.TRS(boxCenter, boxRotation, boxSize);
Ray modelRay = ray.InverseTransform(boxMatrix);
if (modelRay.direction.sqrMagnitude == 0.0f) return false;
Bounds unitCube = new Bounds(Vector3.zero, Vector3.one);
if (unitCube.IntersectRay(modelRay, out t))
{
Vector3 intersectPt = boxMatrix.MultiplyPoint(modelRay.GetPoint(t));
t = (intersectPt - ray.origin).magnitude;
return true;
}
return false;
/*t = 0.0f;
boxSize += epsilon.SizeEps;
Matrix4x4 boxMatrix = Matrix4x4.TRS(boxCenter, boxRotation, boxSize);
Ray modelRay = ray.InverseTransform(boxMatrix);
if (modelRay.direction.sqrMagnitude == 0.0f) return false;
Vector3 rayOrigin = modelRay.origin;
Vector3 rayDirection = modelRay.direction;
Vector3 rayDirectionRec = new Vector3(1.0f / rayDirection.x, 1.0f / rayDirection.y, 1.0f / rayDirection.z);
float halfBoxWidth = 0.5f;
float halfBoxHeight = 0.5f;
float halfBoxDepth = 0.5f;
Vector3 boxExtentsMin = new Vector3(-halfBoxWidth, -halfBoxHeight, -halfBoxDepth);
Vector3 boxExtentsMax = new Vector3(halfBoxWidth, halfBoxHeight, halfBoxDepth);
float highestMinimumT = float.MinValue;
float lowestMaximumT = float.MaxValue;
for (int slabIndex = 0; slabIndex < 3; ++slabIndex)
{
if (Mathf.Abs(rayDirection[slabIndex]) > 1e-4f)
{
float minimumT = (boxExtentsMin[slabIndex] - rayOrigin[slabIndex]) * rayDirectionRec[slabIndex];
float maximumT = (boxExtentsMax[slabIndex] - rayOrigin[slabIndex]) * rayDirectionRec[slabIndex];
if (minimumT > maximumT)
{
float temp = minimumT;
minimumT = maximumT;
maximumT = temp;
}
if (minimumT > highestMinimumT) highestMinimumT = minimumT;
if (maximumT < lowestMaximumT) lowestMaximumT = maximumT;
if (highestMinimumT > lowestMaximumT) return false;
if (lowestMaximumT < 0.0f) return false;
}
else
{
if (rayOrigin[slabIndex] < boxExtentsMin[slabIndex] ||
rayOrigin[slabIndex] > boxExtentsMax[slabIndex]) return false;
}
}
t = highestMinimumT;
if (t < 0.0f) t = lowestMaximumT;
return true;*/
}
public static bool ContainsPoint(Vector3 point, Vector3 boxCenter, Vector3 boxSize, Quaternion boxRotation, BoxEpsilon epsilon = new BoxEpsilon())
{
boxSize += epsilon.SizeEps;
Matrix4x4 boxMatrix = Matrix4x4.TRS(boxCenter, boxRotation, boxSize);
point = boxMatrix.inverse.MultiplyPoint(point);
return point.x >= -0.5f && point.x <= 0.5f &&
point.y >= -0.5f && point.y <= 0.5f &&
point.z >= -0.5f && point.z <= 0.5f;
}
}
}