Squashed 'Assets/NanoBrain/LinearAlgebra/' changes from 672f8bf..a0bbb5b

a0bbb5b Improve unit tests
f3044db Add more ToString
e64eaa2 Fix direction  To Unity Vector3
3bcd212 Fixed Spherical.ToVector3
a232385 Merge commit '841d923fed686700610a85aeab6289e44239aa6c'

git-subtree-dir: Assets/NanoBrain/LinearAlgebra
git-subtree-split: a0bbb5b5dd2e938be6d8a516bb69eb1130c4a51d
This commit is contained in:
Pascal Serrarens 2026-01-07 14:33:10 +01:00
parent 841d923fed
commit 1855aa0705
6 changed files with 107 additions and 50 deletions

View File

@ -57,7 +57,7 @@ namespace LinearAlgebra {
public readonly float inRevolutions => this.value / 360.0f;
public override string ToString() {
return $"{this.inDegrees} deg.";
return $"{this.inDegrees}\u00B0";
}
public static readonly AngleFloat zero = Degrees(0);

View File

@ -3,7 +3,8 @@ using System;
using Vector3Float = UnityEngine.Vector3;
#endif
namespace LinearAlgebra {
namespace LinearAlgebra
{
/// <summary>
/// A direction in 3D space
@ -16,7 +17,8 @@ namespace LinearAlgebra {
/// rotation has been applied.
/// The angles are automatically normalized to stay within the abovenmentioned
/// ranges.
public struct Direction {
public struct Direction
{
/// @brief horizontal angle, range = (-180..180] degrees
public AngleFloat horizontal;
/// @brief vertical angle, range in degrees = (-90..90] degrees
@ -29,7 +31,8 @@ namespace LinearAlgebra {
/// <param name="vertical">The vertical angle</param>
/// <remarks>The direction will be normalized automatically
/// to ensure the angles are within the allowed ranges</remarks>
public Direction(AngleFloat horizontal, AngleFloat vertical) {
public Direction(AngleFloat horizontal, AngleFloat vertical)
{
this.horizontal = horizontal;
this.vertical = vertical;
this.Normalize();
@ -43,8 +46,10 @@ namespace LinearAlgebra {
/// <returns>The direction</returns>
/// <remarks>The direction will be normalized automatically
/// to ensure the angles are within the allowed ranges</remarks>
public static Direction Degrees(float horizontal, float vertical) {
Direction d = new() {
public static Direction Degrees(float horizontal, float vertical)
{
Direction d = new()
{
horizontal = AngleFloat.Degrees(horizontal),
vertical = AngleFloat.Degrees(vertical)
};
@ -57,8 +62,10 @@ namespace LinearAlgebra {
/// <param name="horizontal">The horizontal angle in radians</param>
/// <param name="vertical">The vertical angle in radians</param>
/// <returns>The direction</returns>
public static Direction Radians(float horizontal, float vertical) {
Direction d = new() {
public static Direction Radians(float horizontal, float vertical)
{
Direction d = new()
{
horizontal = AngleFloat.Radians(horizontal),
vertical = AngleFloat.Radians(vertical)
};
@ -99,8 +106,10 @@ namespace LinearAlgebra {
/// </summary>
public readonly static Direction right = Degrees(90, 0);
private void Normalize() {
if (this.vertical > AngleFloat.deg90 || this.vertical < -AngleFloat.deg90) {
private void Normalize()
{
if (this.vertical > AngleFloat.deg90 || this.vertical < -AngleFloat.deg90)
{
this.horizontal += AngleFloat.deg180;
this.vertical = AngleFloat.Degrees(180 - this.vertical.inDegrees);
}
@ -118,9 +127,11 @@ namespace LinearAlgebra {
// Calculate Vector
float cosV = MathF.Cos(radV);
float x = cosV * MathF.Cos(radH);
float y = MathF.Sin(radV);
float z = cosV * MathF.Sin(radH);
float sinV = MathF.Sin(radV);
float x = cosV * MathF.Sin(radH);
float y = sinV;
float z = cosV * MathF.Cos(radH);
return new UnityEngine.Vector3(x, y, z);
}
@ -168,7 +179,8 @@ namespace LinearAlgebra {
/// <returns>The direction</returns>
/// <remarks>Information about the length of the carthesian vector is not
/// included in this transformation</remarks>
public static Direction FromVector3(Vector3Float v) {
public static Direction FromVector3(Vector3Float v)
{
AngleFloat horizontal = AngleFloat.Atan2(v.horizontal, v.depth);
AngleFloat vertical = AngleFloat.deg90 - AngleFloat.Acos(v.vertical);
Direction d = new(horizontal, vertical);
@ -183,7 +195,8 @@ namespace LinearAlgebra {
/// <param name="d1"></param>
/// <param name="d2"></param>
/// <returns>True when the direction angles are equal, false otherwise.</returns>
public static bool operator ==(Direction d1, Direction d2) {
public static bool operator ==(Direction d1, Direction d2)
{
bool horizontalEq = d1.horizontal == d2.horizontal;
bool verticalEq = d1.vertical == d2.vertical;
return horizontalEq && verticalEq;
@ -195,13 +208,15 @@ namespace LinearAlgebra {
/// <param name="d1"></param>
/// <param name="d2"></param>
/// <returns>True when the direction angles are not equal, false otherwise.</returns>
public static bool operator !=(Direction d1, Direction d2) {
public static bool operator !=(Direction d1, Direction d2)
{
bool horizontalNEq = d1.horizontal != d2.horizontal;
bool verticalNEq = d1.vertical != d2.vertical;
return horizontalNEq || verticalNEq;
}
public override readonly bool Equals(object obj) {
public override readonly bool Equals(object obj)
{
if (obj is not Direction d)
return false;
@ -211,7 +226,8 @@ namespace LinearAlgebra {
}
public override readonly int GetHashCode() {
public override readonly int GetHashCode()
{
return HashCode.Combine(horizontal, vertical);
}

View File

@ -62,28 +62,34 @@ namespace LinearAlgebra {
#if UNITY_5_3_OR_NEWER
public static Spherical FromVector3(Vector3 v) {
float distance = v.magnitude;
if (distance == 0.0f)
return Spherical.zero;
else {
float verticalAngle = (float)(Math.PI / 2 - Math.Acos(v.y / distance)) * AngleFloat.Rad2Deg;
float horizontalAngle = (float)Math.Atan2(v.x, v.z) * AngleFloat.Rad2Deg;
return Degrees(distance, horizontalAngle, verticalAngle);
}
// if (distance == 0.0f)
// return Spherical.zero;
// else {
// float verticalAngle = (float)(Math.PI / 2 - Math.Acos(v.y / distance)) * AngleFloat.Rad2Deg;
// float horizontalAngle = (float)Math.Atan2(v.x, v.z) * AngleFloat.Rad2Deg;
// return Degrees(distance, horizontalAngle, verticalAngle);
// }
Direction direction = Direction.FromVector3(v.normalized);
return new Spherical(distance, direction);
}
public readonly Vector3 ToVector3() {
float verticalRad = (AngleFloat.deg90 - this.direction.vertical).inRadians;
float horizontalRad = this.direction.horizontal.inRadians;
float cosVertical = (float)Math.Cos(verticalRad);
float sinVertical = (float)Math.Sin(verticalRad);
float cosHorizontal = (float)Math.Cos(horizontalRad);
float sinHorizontal = (float)Math.Sin(horizontalRad);
// float verticalRad = (AngleFloat.deg90 - this.direction.vertical).inRadians;
// float horizontalRad = this.direction.horizontal.inRadians;
// float cosVertical = (float)Math.Cos(verticalRad);
// float sinVertical = (float)Math.Sin(verticalRad);
// float cosHorizontal = (float)Math.Cos(horizontalRad);
// float sinHorizontal = (float)Math.Sin(horizontalRad);
float x = this.distance * sinVertical * sinHorizontal;
float y = this.distance * cosVertical;
float z = this.distance * sinVertical * cosHorizontal;
// float x = this.distance * sinVertical * sinHorizontal;
// float y = this.distance * cosVertical;
// float z = this.distance * sinVertical * cosHorizontal;
Vector3 v = new(x, y, z);
// Vector3 v = new(x, y, z);
// return v;
Vector3 v = this.direction.ToVector3();
v *= this.distance;
return v;
}
#else
@ -99,22 +105,29 @@ namespace LinearAlgebra {
}
public readonly Vector3Float ToVector3() {
float verticalRad = (AngleFloat.deg90 - this.direction.vertical).inRadians;
float horizontalRad = this.direction.horizontal.inRadians;
float cosVertical = (float)Math.Cos(verticalRad);
float sinVertical = (float)Math.Sin(verticalRad);
float cosHorizontal = (float)Math.Cos(horizontalRad);
float sinHorizontal = (float)Math.Sin(horizontalRad);
// float verticalRad = (AngleFloat.deg90 - this.direction.vertical).inRadians;
// float horizontalRad = this.direction.horizontal.inRadians;
// float cosVertical = (float)Math.Cos(verticalRad);
// float sinVertical = (float)Math.Sin(verticalRad);
// float cosHorizontal = (float)Math.Cos(horizontalRad);
// float sinHorizontal = (float)Math.Sin(horizontalRad);
float x = this.distance * sinVertical * sinHorizontal;
float y = this.distance * cosVertical;
float z = this.distance * sinVertical * cosHorizontal;
// float x = this.distance * sinVertical * sinHorizontal;
// float y = this.distance * cosVertical;
// float z = this.distance * sinVertical * cosHorizontal;
Vector3Float v = new(x, y, z);
// Vector3Float v = new(x, y, z);
Vector3Float v = this.direction.ToVector3();
v *= this.distance;
return v;
}
#endif
public override readonly string ToString() {
return $"Spherical({this.distance}, h: {this.direction.horizontal}, v: {this.direction.vertical})";
}
public readonly float magnitude => this.distance;
public Spherical normalized {
@ -228,7 +241,7 @@ namespace LinearAlgebra {
return Spherical.Radians(rAvg, azAvgRad, elAvgRad);
}
/*
public static Spherical Average(List<Spherical> vectors) {
// float sumSinPhiCosTheta = 0.0f;
// float sumSinPhiSinTheta = 0.0f;
@ -302,9 +315,9 @@ namespace LinearAlgebra {
return new Spherical(rAvg, new Direction(horizontalAvg, verticalAvg));
}
*/
/*
public static Spherical Average(IEnumerable<Spherical> vectors) {
const float EPS = 1e-6f;
if (vectors == null) throw new ArgumentNullException(nameof(vectors));
@ -371,7 +384,7 @@ namespace LinearAlgebra {
return Spherical.Radians(rAvgFinal, azAvg, elAvg);
}
*/
}
}

View File

@ -119,6 +119,10 @@ namespace LinearAlgebra {
return new Vector3Float(horizontal, vertical, depth);
}
public override string ToString() {
return $"({this.horizontal}, {this.vertical}, {this.depth})";
}
/// <summary>
/// A vector with zero for all axis
/// </summary>

View File

@ -103,7 +103,7 @@ namespace LinearAlgebra.Test {
[Test]
public void ToVector3AndBack2() {
Direction d1 = Direction.Degrees(135, 85);
Direction d1 = Direction.Degrees(-135, 85);
Vector3Float v = d1.ToVector3();
Direction d2 = Direction.FromVector3(v);
Assert.AreEqual(d1.horizontal.inDegrees, d2.horizontal.inDegrees, 0.0001f);

View File

@ -234,6 +234,30 @@ namespace LinearAlgebra.Test {
Assert.AreEqual(MathF.Cos(alpha), avg.distance, 1e-4f);
}
[Test]
public void Average_CompareWithVector3() {
// Four vectors around azimuth 0, pi/2, pi, 3pi/2 at same polar angle from vertical (alpha)
float alpha = MathF.PI / 6f; // polar angle from vertical
float elevation = MathF.PI / 2f - alpha; // convert polar-from-vertical to elevation
List<Spherical> dirs = new List<Spherical> {
new(1f, Direction.Radians(0f, elevation)),
new(2f, Direction.Radians(MathF.PI/2, elevation+1)),
new(3f, Direction.Radians(MathF.PI, elevation+2)),
new(4f, Direction.Radians(3*MathF.PI/2, elevation+3))
};
Spherical avg = Spherical.Average(dirs);
Vector3Float r = Vector3Float.zero;
foreach (Spherical dir in dirs) {
r += dir.ToVector3();
}
r = r / 4;
Spherical avg2 = Spherical.FromVector3(r);
Assert.AreEqual(avg, avg2);
}
}
}
#endif