feat(core): Phase B.3 — PortalPlane (plane math + crossing detection)
Adds the foundational portal-plane record for cell transition detection. PortalPlane.FromVertices computes a normalised plane from 3 coplanar polygon vertices via cross product + dot product; IsCrossing tests whether a movement vector straddles the plane (strictly negative dot-product product — exact-on-plane position returns false as specified). 4 new unit tests: normal construction, opposite-side crossing, same-side no-crossing, start-on-plane no-crossing. All 269 tests green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e4f3f6bfab
commit
cb46d892d5
2 changed files with 112 additions and 0 deletions
45
src/AcDream.Core/Physics/PortalPlane.cs
Normal file
45
src/AcDream.Core/Physics/PortalPlane.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.Core.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// A portal plane derived from an EnvCell's CellPortal polygon.
|
||||
/// Used to detect when a player crosses from one cell into another.
|
||||
/// </summary>
|
||||
public readonly record struct PortalPlane(
|
||||
Vector3 Normal,
|
||||
float D,
|
||||
uint TargetCellId, // OtherCellId — the cell on the far side (0xFFFF = outdoor)
|
||||
uint OwnerCellId, // the EnvCell that owns this portal
|
||||
ushort Flags) // PortalFlags value
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a PortalPlane from three coplanar vertices (winding order
|
||||
/// determines the normal direction via cross product).
|
||||
/// </summary>
|
||||
public static PortalPlane FromVertices(
|
||||
Vector3 v0, Vector3 v1, Vector3 v2,
|
||||
uint targetCellId, uint ownerCellId, ushort flags)
|
||||
{
|
||||
var edge1 = v1 - v0;
|
||||
var edge2 = v2 - v0;
|
||||
var normal = Vector3.Normalize(Vector3.Cross(edge1, edge2));
|
||||
float d = -Vector3.Dot(normal, v0);
|
||||
return new PortalPlane(normal, d, targetCellId, ownerCellId, flags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the movement from <paramref name="oldPos"/> to
|
||||
/// <paramref name="newPos"/> crosses this plane (the two positions are on
|
||||
/// strictly opposite sides). A position exactly on the plane (distance = 0)
|
||||
/// does NOT count as a crossing.
|
||||
/// </summary>
|
||||
public bool IsCrossing(Vector3 oldPos, Vector3 newPos)
|
||||
{
|
||||
float oldDist = Vector3.Dot(Normal, oldPos) + D;
|
||||
float newDist = Vector3.Dot(Normal, newPos) + D;
|
||||
// Strictly negative product → opposite signs → crossed the plane.
|
||||
// If either distance is exactly 0 the product is 0, not negative → no crossing.
|
||||
return oldDist * newDist < 0f;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue