acdream/src/AcDream.Core/Physics/RetailFrameMath.cs
Erik f02b995e4f feat(physics): port retail projectile integration
Add a pure Core projectile driver for retail clock quanta, final-state acceleration, 50-unit velocity clamping, world-space angular integration, full-3D AlignPath, and oriented Setup-local sphere sweeps. Preserve exact success versus set_frame-only failure semantics, independent Contact/OnWalkable state, and full-cell identity across all landblock directions.

Port OBJECTINFO::missile_ignore with explicit weenie/creature metadata, remove the invented transition-step cap, retain PathClipped without arming PerfectClip, and keep authoritative target identity optional. Add malformed-shape/clock, thin-wall, terrain, target-exclusion, frame-spike, failure-state, and boundary conformance coverage.

Retire TS-2 and synchronize the architecture, milestones, roadmap, research oracle, and durable physics memory.

Co-Authored-By: Codex <noreply@openai.com>
2026-07-14 11:43:11 +02:00

63 lines
2.6 KiB
C#

using System.Numerics;
namespace AcDream.Core.Physics;
/// <summary>
/// Retail frame-orientation helpers used by physics presentation.
/// </summary>
public static class RetailFrameMath
{
/// <summary>
/// Points the frame's local +Y axis along a world-space direction with
/// zero roll. This is the observable contract of retail
/// <c>Frame::set_vector_heading</c> at <c>0x00535DB0</c>: normalize the
/// full three-dimensional vector, derive compass yaw plus elevation, and
/// replace the frame rotation. A zero-length vector leaves the prior
/// orientation unchanged.
/// </summary>
public static Quaternion SetVectorHeading(
Quaternion currentOrientation,
Vector3 direction)
{
float lengthSquared = direction.LengthSquared();
if (lengthSquared < PhysicsGlobals.EpsilonSq || !float.IsFinite(lengthSquared))
return currentOrientation;
Vector3 forward = direction / MathF.Sqrt(lengthSquared);
// Retail's zero-roll Euler construction is equivalent to choosing a
// horizontal right axis from the compass heading, then deriving the
// remaining up axis. At a perfectly vertical heading atan2(0, 0)
// resolves to zero, so retain +X as the deterministic right axis.
Vector3 right;
if (forward.X == 0f && forward.Y == 0f)
{
right = Vector3.UnitX;
}
else
{
// Normalize the horizontal pair without squaring the original
// components. That preserves every non-zero compass component,
// including a nearly vertical vector below PhysicsGlobals.EPSILON,
// just as retail's atan2-based construction does.
float scale = MathF.Max(MathF.Abs(forward.X), MathF.Abs(forward.Y));
float x = forward.X / scale;
float y = forward.Y / scale;
float horizontalLength = MathF.Sqrt((x * x) + (y * y));
right = new Vector3(y / horizontalLength, -x / horizontalLength, 0f);
}
Vector3 up = Vector3.Normalize(Vector3.Cross(right, forward));
// System.Numerics uses row-vector transforms. Each row below is the
// world direction of one local basis axis: +X right, +Y heading,
// +Z up.
var rotation = new Matrix4x4(
right.X, right.Y, right.Z, 0f,
forward.X, forward.Y, forward.Z, 0f,
up.X, up.Y, up.Z, 0f,
0f, 0f, 0f, 1f);
return Quaternion.Normalize(Quaternion.CreateFromRotationMatrix(rotation));
}
}