stride3d / stride3d/stride

[RFC] Stride.Math with the new .NET 7 System.Numerics interfaces

Open
#1,570 5 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
C#
Stars
7.8k
Forks
1.2k
Avg merge
2d 17h
Merged PRs (30d)
49

Description

Using the newly added abstract static method for interfaces we can reduce code repetition.
This shouldn't affect anything but number of lines of code for the vector implementations.

Some questions to answer before going forward with it :

  • Is it usable and comfortable ?
  • Is it viable performance wise ?
  • Can we do SIMD with it ?
  • Does it allow inlining ?
Implementation
using System;

namespace Stride.Maths

public struct Double2 : IVector2<Double2, double>
{
    public double X {get;set;}
    public double Y {get;set;}

    public Double2(double value)
    {
        X = value;
        Y = value;
    }
    public Double2(double x, double y)
    {
        X = x;
        Y = y;
    }

    public static double Distance(in Double2 value)
    {
        return (double)Math.Sqrt(value.X * value.X + value.Y * value.Y);
    }

    public static Double2 New(double value)
    {
        return new(value);
    }

    public static Double2 New(double x, double y)
    {
        return new(x,y);
    }

    public static void SquareRoot(in Double2 value, out Double2 result)
    {
        result = new((double)Math.Sqrt(value.X), (double)Math.Sqrt(value.Y));
    }
}

public struct Half2 : IVector2<Half2, Half>
{
    public Half X {get;set;}
    public Half Y {get;set;}

    public Half2(Half value)
    {
        X = value;
        Y = value;
    }
    public Half2(Half x, Half y)
    {
        X = x;
        Y = y;
    }

    public static Half Distance(in Half2 value)
    {
        return (Half)MathF.Sqrt((float)(value.X * value.X + value.Y * value.Y));
    }

    public static Half2 New(Half value)
    {
        return new(value);
    }

    public static Half2 New(Half x, Half y)
    {
        return new(x,y);
    }

    public static void SquareRoot(in Half2 value, out Half2 result)
    {
        result = new((Half)MathF.Sqrt((float)value.X), (Half)MathF.Sqrt((float)value.Y));
    }
}
Interface
using System.Numerics;

internal interface IVector2<T, Num>
    where T :
        struct,
        IVector2<T, Num>
    where Num : INumber<Num>
{
    public Num X { get; set; }
    public Num Y { get; set; }
    public T One => T.New(Num.One);
    public T Zero => T.New(Num.Zero);

    public static abstract T New(Num value);
    public static abstract T New(Num x, Num y);
    public static virtual void Abs(in T value, out T result)
    {
        result = T.New(Num.Abs(value.X), Num.Abs(value.Y));
    }

    public static virtual void Add(in T left, Num scalar, out T result)
    {
        result = T.New(
            left.X + scalar,
            left.Y + scalar
        );
    }
    public static virtual void Add(in T left, in T right, out T result)
    {
        result = T.New(
            left.X + right.X,
            left.Y + right.Y
        );
    }

    public static virtual void Clamp(in T value, in T min, in T max, out T result)
    {
        result = T.New(
            Num.MaxMagnitude(Num.MinMagnitude(value.X,max.X), min.X),
            Num.MaxMagnitude(Num.MinMagnitude(value.Y,max.Y), min.Y)
        );
    }
    public static abstract Num Distance(in T value);
    public static virtual Num DistanceSquared(in T value)
    {
        return value.X * value.X + value.Y * value.Y;
    }
    public static virtual void Divide(Num scalar, in T value, out T result)
    {
        result = T.New(
            scalar / value.X,
            scalar / value.Y
        );
    }
    public static virtual void Divide(in T value, Num scalar, out T result)
    {
        result = T.New(
            value.X / scalar,
            value.Y / scalar
        );
    }
    public static virtual void Divide(in T left, in T right, out T result)
    {
        result = T.New(
            left.X / right.X,
            left.Y / right.Y
        );
    }
    public static virtual Num Dot(in T left, in T right)
    {
        return left.X * right.X + left.Y * right.Y;
    }
    public static virtual void Lerp(in T value1, in T value2, Num amount, out T result)
    {
        T.Subtract(value2, value1, out var sub);
        T.Multiply(sub, amount, out var mul);
        T.Add(value1, mul, out result);
    }
    public static virtual void Multiply(in T value, Num scalar, out T result)
    {
        result = T.New(
            value.X * scalar,
            value.Y * scalar
        );
    }
    public static virtual void Multiply(in T left, in T right, out T result)
    {
        result = T.New(
            left.X * right.X,
            left.Y * right.Y
        );
    }
    public static virtual void Negate(in T value, out T result)
    {
        result = T.New(
            -value.X,
            -value.Y
        );
    }
    public static virtual void Normalize(in T value, out T result)
    {
        var x = value.X;
        var y = value.Y;
        result = T.New(
            x < -Num.One ? -Num.One : x > Num.One ? Num.One : x, 
            x < -Num.One ? -Num.One : x > Num.One ? Num.One : y 
        );
    }
    public static virtual void Reflect(in T vector, in T normal, out T result)
    {
        Num dot = T.Dot(vector,normal);
        result = T.New(
            vector.X - (Num.One + Num.One) * dot * normal.X,
            vector.Y - (Num.One + Num.One) * dot * normal.Y
        );
    }
    public static abstract void SquareRoot(in T value, out T result);
    
    public static virtual void Subtract(in T left, Num scalar, out T result)
    {
        result = T.New(
            left.X - scalar,
            left.Y - scalar
        );
    }
    public static virtual void Subtract(Num scalar, in T left, out T result)
    {
        result = T.New(
            scalar - left.X,
            scalar - left.Y
        );
    }
    public static virtual void Subtract(in T left, in T right, out T result)
    {
        result = T.New(
            left.X - right.X,
            left.Y - right.Y
        );
    }

    public static virtual void Transform(in T vector, in T transform, out T result)
    {
        T.Add(vector,transform, out result);
    }
    // public static virtual void TransformNormal(in T left, in T right, out T result);
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the Stride.Maths vector implementations shown for Double2 and Half2, together with the proposed IVector2<T, Num> interface. Evaluate the listed questions about usability, performance, SIMD, and inlining before deciding whether the approach is viable. Done means reaching a documented decision on the RFC and its scope.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
game-dev
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.