dotnet / dotnet/runtime

[API Proposal]: Add turn-based Matrix/Quaternion rotation overloads.

Open
#132,611 3 comments 0 reactions 0 assignees View on GitHub
api-suggestion area-System.Numerics
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Background and motivation

As `SinPi` and `CosPi` methods were introduced for https://github.com/dotnet/runtime/issues/20137, it would make sense and be helpful to expose equivalent tun-based rotation APIs in `System.Numerics` types, based on these APIs.
As a reminder, you can check this article for the benefits of using `SinPi` / `CosPi` over radians: https://www.computerenhance.com/p/turns-are-better-than-radians

A very simple example of why this is useful can be seen by comparing the output of cosine for 90° in either method:

```
> float.Cos(MathF.PI / 2)
-4,371139E-08
> float.Cos(float.DegreesToRadians(90))
-4,371139E-08
> float.CosPi(0.5f)
0
```

Thus, by providing turn-based rotation, we would at least get exact 90° rotation matrices in quite a few cases.

The implementation of most of those methods should be pretty trivial, as it only requires replacing the `Sin`, `Cos` and `SinCos` methods by their `SinPi`, `CosPi` and `SinCosPi` equivalents.

### API Proposal

```csharp
namespace System.Numerics;

public struct Matrix4x4
{
// Code based on CreateRotationX
public static Matrix4x4 CreateRotationTurnsX(float turns);
// Code based on CreateRotationX
public static Matrix4x4 CreateRotationTurnsX(float turns, Vector3 centerPoint);
// Code based on CreateRotationY
public static Matrix4x4 CreateRotationTurnsY(float turns);
// Code based on CreateRotationY
public static Matrix4x4 CreateRotationTurnsY(float turns, Vector3 centerPoint);
// Code based on CreateRotationZ
public static Matrix4x4 CreateRotationTurnsZ(float turns);
// Code based on CreateRotationZ
public static Matrix4x4 CreateRotationTurnsZ(float turns, Vector3 centerPoint);
// Code based on CreateFromAxisAngle
public static Matrix4x4 CreateFromAxisTurns(Vector3 axis, float turns);
}

public struct Matrix3x2
{
// Code based on CreateRotation
public static Matrix3x2 CreateRotationTurns(float turns);
// Code based on CreateRotation
public static Matrix3x2 CreateRotationTurns(float turns, Vector2 centerPoint);
}

public struct Quaternion
{
// Code based on CreateFromAxisAngle
public static Quaternion CreateFromAxisTurns(Vector3 axis, float turns);
}
```

Implementation would go as follows, taking for example `Matrix4x4.CreateRotationY`:
```csharp
// Original method, currently present in Matrix4x4:

/// Creates a matrix for rotating points around the Y axis.
/// The amount, in radians, by which to rotate around the Y-axis.
/// The rotation matrix.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix4x4 CreateRotationY(float radians)
{
(float s, float c) = float.SinCos(radians);

// [ c 0 -s 0 ]
// [ 0 1 0 0 ]
// [ s 0 c 0 ]
// [ 0 0 0 1 ]

Impl result;

result.X = Vector128.Create(c, 0, -s, 0);
result.Y = Vector128.Create(0.0f, 1.0f, 0.0f, 0.0f);
result.Z = Vector128.Create(s, 0, c, 0);
result.W = Vector128.Create(0.0f, 0.0f, 0.0f, 1.0f);

return result.AsM4x4();
}

// New method based on the one above, where SinCos has been replaced by SinCosPi.
// Names need to differ because parameter types are identical.

/// Creates a matrix for rotating points around the Y axis.
/// The amount, in turns, by which to rotate around the Y-axis.
/// The rotation matrix.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix4x4 CreateRotationTurnsY(float turns)
{
(float s, float c) = float.SinCosPi(turns);

// [ c 0 -s 0 ]
// [ 0 1 0 0 ]
// [ s 0 c 0 ]
// [ 0 0 0 1 ]

Impl result;

result.X = Vector128.Create(c, 0, -s, 0);
result.Y = Vector128.Create(0.0f, 1.0f, 0.0f, 0.0f);
result.Z = Vector128.Create(s, 0, c, 0);
result.W = Vector128.Create(0.0f, 0.0f, 0.0f, 1.0f);

return result.AsM4x4();
}

```

### API Usage

```csharp
// Previously Matrix4x4.CreateRotationY(MathF.PI / 4);
var world = Matrix4x4.CreateRotationTurnsY(0.125);
```

Currently, one has to either continue converting their rotations into radians, thus losing precision in some cases, or reimplement the rotation factories themselves, possibly losing some optimizations on the way.

These new APIs would of course not be applicable in all scenarios, but one of the basic programming scenarios is to compute oscillations or rotations using the well known `ω = 2πf`. In this case, we could get rid of the multiplication by `π` and in many cases, `ω` may remain an integer value with integer precision:

```csharp
// Previously
Matrix4x4 GetCurrentRotation(int freq, float timeInSeconds)
{
// This value would likely be stored in some field rather than passed as parameter and computed everytime.
float ω = MathF.Tau * freq;
return Matrix4x4.CreateRotationY(ω * timeInSeconds);
}

// New
Matrix4x4 GetCurrentRotation(int freq, float timeInSeconds)
{
// This value would likely be stored in some field rather than passed as parameter and computed everytime.
// While still an integer value, it is worth storing as a float, as it has to be a float before being multiplied anyway.
float ω = 2 * freq;
return Matrix4x4.CreateRotationTurnsY(ω * timeInSeconds);
}
```

### Alternative Designs

Outside of naming considerations, which can of course be discussed, I don't think there is another way to provide this feature.

### Risks

_No response_

Contributor guide

Open the contributing guide

Research direction

Start with the existing System.Numerics Matrix4x4, Matrix3x2, and Quaternion CreateRotation and CreateFromAxisAngle entry points, then review the referenced SinPi, CosPi, and SinCosPi APIs. Compare the proposed turn-based overloads and naming across the listed types; done means the API design and scope are resolved and the resulting behavior is specified.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend-api-design, computer-graphics
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.