Pascal Serrarens 8e87e4ea77 Squashed 'NanoBrain/' content from commit b3423b9
git-subtree-dir: NanoBrain
git-subtree-split: b3423b99a752cdabbc4e7c51565fb54425481feb
2026-04-07 09:12:29 +02:00

271 lines
12 KiB
C#

//#if !UNITY_5_6_OR_NEWER
using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace LinearAlgebra.Test {
public class SphericalTest {
[SetUp]
public void Setup() {
}
[Test]
public void FromVector3() {
#if UNITY_5_6_OR_NEWER
UnityEngine.Vector3 v = new(0, 0, 1);
#else
Vector3Float v = new(0, 0, 1);
#endif
Spherical s = Spherical.FromVector3(v);
Assert.AreEqual(1.0f, s.distance, "s.distance 0 0 1");
Assert.AreEqual(0.0f, s.direction.horizontal.inDegrees, "s.hor 0 0 1");
Assert.AreEqual(0.0f, s.direction.vertical.inDegrees, 1.0E-05F, "s.vert 0 0 1");
v = new(0, 1, 0);
s = Spherical.FromVector3(v);
Assert.AreEqual(1.0f, s.distance, "s.distance 0 1 0");
Assert.AreEqual(0.0f, s.direction.horizontal.inDegrees, "s.hor 0 1 0");
Assert.AreEqual(90.0f, s.direction.vertical.inDegrees, "s.vert 0 1 0");
v = new(1, 0, 0);
s = Spherical.FromVector3(v);
Assert.AreEqual(1.0f, s.distance, "s.distance 1 0 0");
Assert.AreEqual(90.0f, s.direction.horizontal.inDegrees, "s.hor 1 0 0");
Assert.AreEqual(0.0f, s.direction.vertical.inDegrees, 1.0E-05F, "s.vert 1 0 0");
}
[Test]
public void Addition() {
Spherical v1 = Spherical.Degrees(1, 45, 0);
Spherical v2 = Spherical.zero;
Spherical r = Spherical.zero;
r = v1 + v2;
Assert.AreEqual(v1.distance, r.distance, 1.0E-05F, "Addition(0,0,0)");
r = v1;
r += v2;
Assert.AreEqual(v1.distance, r.distance, 1.0E-05F, "Addition(0,0,0)");
v2 = Spherical.Degrees(1, 0, 90);
r = v1 + v2;
Assert.AreEqual(Math.Sqrt(2), r.distance, 1.0E-05F, "Addition(1 0 90)");
Assert.AreEqual(45.0f, r.direction.horizontal.inDegrees, 1e-5f, "Addition(1 0 90)");
Assert.AreEqual(45.0f, r.direction.vertical.inDegrees, 1.0E-05F, "Addition(1 0 90)");
}
[Test]
public void Average2_IdenticalVectors() {
Direction dir = Direction.Radians(MathF.PI / 4f, MathF.PI / 6f);
Spherical v = new(2.5f, dir);
Spherical avg = Spherical.Average(v, v);
Assert.AreEqual(2.5f, avg.distance, 1e-5f);
Assert.AreEqual(dir.horizontal, avg.direction.horizontal);
Assert.AreEqual(dir.vertical, avg.direction.vertical);
}
[Test]
public void Average2_OppositeUnitVectors() {
// Two opposite vectors: same distance, horizontal opposite (pi apart), same vertical
Spherical v1 = Spherical.Radians(1f, 0f, 0f);
Spherical v2 = Spherical.Radians(1f, MathF.PI, 0f);
Spherical avg = Spherical.Average(v1, v2);
Assert.AreEqual(0f, avg.distance, 1e-4f);
// When distance is zero, angles may be undefined; allow any angle but ensure near-zero magnitude
}
[Test]
public void Average2_WeightedByDistance() {
// Two vectors same direction but different distances -> weighted average distance
Direction dir = Direction.Radians(MathF.PI / 3f, MathF.PI / 4f);
Spherical a = new(1f, dir);
Spherical b = new(3f, dir);
Spherical avg = Spherical.Average(a, b);
// average distance should be (1+3)/2 = 2
Assert.AreEqual(2f, avg.distance, 1e-5f);
Assert.AreEqual(dir.horizontal.inRadians, avg.direction.horizontal.inRadians, 1e-5f);
Assert.AreEqual(dir.vertical.inRadians, avg.direction.vertical.inRadians, 1e-5f);
}
[Test]
public void Average2_OppositeButNotExact_NotZero() {
// Nearly opposite but not exact; expect a valid averaged direction and averaged distance
Direction d1 = Direction.Radians(0f, 0f);
Direction d2 = Direction.Radians(MathF.PI - 1e-3f, 0.0f); // slight offset
Spherical v1 = new(2.0f, d1);
Spherical v2 = new(4.0f, d2);
Spherical avg = Spherical.Average(v1, v2);
// Distance is arithmetic mean
Assert.AreEqual(3.0f, avg.distance, 1e-5f);
// Averaged azimuth should be near +pi/2 or -pi/2? we can check it's not NaN and unit-vector properties hold
float ux = MathF.Cos(avg.direction.horizontal.inRadians) * MathF.Cos(avg.direction.vertical.inRadians);
float uy = MathF.Sin(avg.direction.horizontal.inRadians) * MathF.Cos(avg.direction.vertical.inRadians);
float uz = MathF.Sin(avg.direction.vertical.inRadians);
float mag = MathF.Sqrt(ux * ux + uy * uy + uz * uz);
Assert.IsTrue(mag > 0.999f && mag < 1.001f);
}
[Test]
public void Average2_BasicAverageDirectionAndDistance() {
// Two different directions not cancelling: expect vector-average result
Direction d1 = Direction.Radians(MathF.PI / 6f, MathF.PI / 12f); // 30°, 15°
Direction d2 = Direction.Radians(MathF.PI / 3f, MathF.PI / 18f); // 60°, 10°
Spherical v1 = new(2.0f, d1);
Spherical v2 = new(4.0f, d2);
Spherical avg = Spherical.Average(v1, v2);
// Distance is arithmetic mean
Assert.AreEqual(3.0f, avg.distance, 1e-5f);
// Check averaged unit-vector equals normalized sum of unit vectors computed here
float a1 = d1.horizontal.inRadians;
float a2 = d2.horizontal.inRadians;
float e1 = d1.vertical.inRadians;
float e2 = d2.vertical.inRadians;
float cx = MathF.Cos(a1) + MathF.Cos(a2);
float cy = MathF.Sin(a1) + MathF.Sin(a2);
float z1 = MathF.Sin(e1);
float z2 = MathF.Sin(e2);
float cz = z1 + z2;
float mag = MathF.Sqrt(cx * cx + cy * cy + cz * cz);
Assert.IsTrue(mag > 1e-6f);
float ux = cx / mag;
float uy = cy / mag;
float uz = cz / mag;
// Reconstruct direction from avg result
float uxAvg = MathF.Cos(avg.direction.horizontal.inRadians) * MathF.Cos(avg.direction.vertical.inRadians);
float uyAvg = MathF.Sin(avg.direction.horizontal.inRadians) * MathF.Cos(avg.direction.vertical.inRadians);
float uzAvg = MathF.Sin(avg.direction.vertical.inRadians);
Assert.AreEqual(ux, uxAvg, 1e-4f);
Assert.AreEqual(uy, uyAvg, 1e-4f);
Assert.AreEqual(uz, uzAvg, 1e-4f);
}
[Test]
public void Average_IdenticalVectors() {
var dir = Direction.Radians(MathF.PI / 4f, MathF.PI / 6f);
var v = new Spherical(2.5f, dir);
var list = new List<Spherical> { v, v, v };
var avg = Spherical.Average(list);
Assert.AreEqual(2.5f, avg.distance, 1e-5f);
Assert.AreEqual(dir.horizontal, avg.direction.horizontal);
Assert.AreEqual(dir.vertical, avg.direction.vertical);
}
[Test]
public void Average_SingleElement() {
Spherical s = Spherical.Radians(1.234f, 0.3f, -0.7f);
Spherical avg = Spherical.Average(new List<Spherical> { s });
Assert.AreEqual(s.distance, avg.distance, 1e-5f);
Assert.AreEqual(s.direction.horizontal.inRadians, avg.direction.horizontal.inRadians, 1e-5f);
Assert.AreEqual(s.direction.vertical.inRadians, avg.direction.vertical.inRadians, 1e-5f);
}
[Test]
public void Average_OppositeUnitVectors() {
// Two opposite vectors: same distance, horizontal opposite (pi apart), same vertical
Spherical v1 = Spherical.Radians(1f, 0f, 0f);
Spherical v2 = Spherical.Radians(1f, MathF.PI, 0f);
Spherical avg = Spherical.Average(new List<Spherical> { v1, v2 });
Assert.AreEqual(0f, avg.distance, 1e-4f);
// When distance is zero, angles may be undefined; allow any angle but ensure near-zero magnitude
}
[Test]
public void Average_WeightedByDistance() {
// Two vectors same direction but different distances -> weighted average distance
Direction dir = Direction.Radians(MathF.PI / 3f, MathF.PI / 4f);
Spherical a = new(1f, dir);
Spherical b = new(3f, dir);
Spherical avg = Spherical.Average(new List<Spherical> { a, b });
// average distance should be (1+3)/2 = 2
Assert.AreEqual(2f, avg.distance, 1e-5f);
Assert.AreEqual(dir.horizontal.inRadians, avg.direction.horizontal.inRadians, 1e-5f);
Assert.AreEqual(dir.vertical.inRadians, avg.direction.vertical.inRadians, 1e-5f);
}
[Test]
public void Average_AxisSymmetricAroundVertical() {
// Four vectors around azimuth 0, pi/2, pi, 3pi/2 at same elevation (vertical) angle phi
float phi = MathF.PI / 6f; // elevation from horizontal plane
var dirs = new List<Spherical> {
new(1f, Direction.Radians(0f, phi)),
new(1f, Direction.Radians(MathF.PI/2, phi)),
new(1f, Direction.Radians(MathF.PI, phi)),
new(1f, Direction.Radians(3*MathF.PI/2, phi))
};
Spherical avg = Spherical.Average(dirs);
// rAvg should equal r * sin(elevation) = sin(phi)
Assert.AreEqual(MathF.Sin(phi), avg.distance, 1e-4f);
// vertical angle undefined when horizontal xy components cancel; allow any angle but ensure r matches
}
[Test]
public void Average_AxisSymmetricAroundVertical2() {
// 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
var dirs = new List<Spherical> {
new(1f, Direction.Radians(0f, elevation)),
new(1f, Direction.Radians(MathF.PI/2, elevation)),
new(1f, Direction.Radians(MathF.PI, elevation)),
new(1f, Direction.Radians(3*MathF.PI/2, elevation))
};
Spherical avg = Spherical.Average(dirs);
// rAvg should equal r * sin(elevation) which equals cos(alpha)
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);
#if UNITY_5_3_OR_NEWER
UnityEngine.Vector3 r = UnityEngine.Vector3.zero;
#else
Vector3Float r = Vector3Float.zero;
#endif
foreach (Spherical dir in dirs) {
r += dir.ToVector3();
}
r = r / 4;
Spherical avg2 = Spherical.FromVector3(r);
Assert.AreEqual(avg, avg2);
}
}
}
//#endif