diff --git a/Assets/Mirror/Core/Tools/Vector3Half.cs b/Assets/Mirror/Core/Tools/Vector3Half.cs new file mode 100644 index 000000000..88b658128 --- /dev/null +++ b/Assets/Mirror/Core/Tools/Vector3Half.cs @@ -0,0 +1,125 @@ +#pragma warning disable CS0659 // 'Vector3Half' overrides Object.Equals(object o) but does not override Object.GetHashCode() +#pragma warning disable CS0661 // 'Vector3Half' defines operator == or operator != but does not override Object.GetHashCode() + +// Vector3Half by mischa (based on game engine project) +using System; +using System.Runtime.CompilerServices; + +namespace Mirror +{ + public struct Vector3Half + { + public Half x; + public Half y; + public Half z; + + public static readonly Vector3Half zero = new Vector3Half((Half)0, (Half)0, (Half)0); + public static readonly Vector3Half one = new Vector3Half((Half)1, (Half)1, (Half)1); + public static readonly Vector3Half forward = new Vector3Half((Half)0, (Half)0, (Half)1); + public static readonly Vector3Half back = new Vector3Half((Half)0, (Half)0, (Half)(-1)); + public static readonly Vector3Half left = new Vector3Half((Half)(-1), (Half)0, (Half)0); + public static readonly Vector3Half right = new Vector3Half((Half)1, (Half)0, (Half)0); + public static readonly Vector3Half up = new Vector3Half((Half)0, (Half)1, (Half)0); + public static readonly Vector3Half down = new Vector3Half((Half)0, (Half)(-1), (Half)0); + + // constructor ///////////////////////////////////////////////////////// + public Vector3Half(Half x, Half y, Half z) + { + this.x = x; + this.y = y; + this.z = z; + } + + // operators /////////////////////////////////////////////////////////// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3Half operator +(Vector3Half a, Vector3Half b) => + new Vector3Half(a.x + b.x, a.y + b.y, a.z + b.z); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3Half operator -(Vector3Half a, Vector3Half b) => + new Vector3Half(a.x - b.x, a.y - b.y, a.z - b.z); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector3Half operator -(Vector3Half v) => + new Vector3Half(-v.x, -v.y, -v.z); + + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // public static Vector3Half operator *(Vector3Half a, long n) => + // new Vector3Half(a.x * n, a.y * n, a.z * n); + + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // public static Vector3Half operator *(long n, Vector3Half a) => + // new Vector3Half(a.x * n, a.y * n, a.z * n); + + // == returns true if approximately equal (with epsilon). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Vector3Half a, Vector3Half b) => + a.x == b.x && + a.y == b.y && + a.z == b.z; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Vector3Half a, Vector3Half b) => !(a == b); + + // NO IMPLICIT System.Numerics.Vector3Half conversion because double<->float + // would silently lose precision in large worlds. + + // [i] component index. useful for iterating all components etc. + public Half this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + default: throw new IndexOutOfRangeException($"Vector3Half[{index}] out of range."); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + switch (index) + { + case 0: + x = value; + break; + case 1: + y = value; + break; + case 2: + z = value; + break; + default: throw new IndexOutOfRangeException($"Vector3Half[{index}] out of range."); + } + } + } + + // instance functions ////////////////////////////////////////////////// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override string ToString() => $"({x} {y} {z})"; + + // equality //////////////////////////////////////////////////////////// + // implement Equals & HashCode explicitly for performance. + // calling .Equals (instead of "==") checks for exact equality. + // (API compatibility) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Vector3Half other) => + x == other.x && y == other.y && z == other.z; + + // Equals(object) can reuse Equals(Vector4) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object other) => + other is Vector3Half vector4 && Equals(vector4); + +#if UNITY_2021_3_OR_NEWER + // Unity 2019/2020 don't have HashCode.Combine yet. + // this is only to avoid reflection. without defining, it works too. + // default generated by rider + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() => HashCode.Combine(x, y, z); +#endif + } +} diff --git a/Assets/Mirror/Core/Tools/Vector3Half.cs.meta b/Assets/Mirror/Core/Tools/Vector3Half.cs.meta new file mode 100644 index 000000000..5f025e09e --- /dev/null +++ b/Assets/Mirror/Core/Tools/Vector3Half.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b70bf28681a14c65a37793a87c8df33e +timeCreated: 1730890316 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Tools/Vector3HalfTests.cs b/Assets/Mirror/Tests/Editor/Tools/Vector3HalfTests.cs new file mode 100644 index 000000000..300f46d82 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Tools/Vector3HalfTests.cs @@ -0,0 +1,150 @@ +using System; +using NUnit.Framework; + +namespace Mirror.Tests.Tools +{ + public class Vector3HalfTests + { + [Test] + public void Constructor() + { + Vector3Half v = new Vector3Half(); + Assert.That(v.x, Is.EqualTo((Half)0)); + Assert.That(v.y, Is.EqualTo((Half)0)); + Assert.That(v.z, Is.EqualTo((Half)0)); + } + + [Test] + public void ConstructorXYZ() + { + Vector3Half v = new Vector3Half((Half)1, (Half)2, (Half)3); + Assert.That(v.x, Is.EqualTo((Half)1)); + Assert.That(v.y, Is.EqualTo((Half)2)); + Assert.That(v.z, Is.EqualTo((Half)3)); + } + + [Test] + public void OperatorAdd() + { + Vector3Half a = new Vector3Half((Half)1, (Half)2, (Half)3); + Vector3Half b = new Vector3Half((Half)2, (Half)3, (Half)4); + Assert.That(a + b, Is.EqualTo(new Vector3Half((Half)3, (Half)5, (Half)7))); + } + + [Test] + public void OperatorSubtract() + { + Vector3Half a = new Vector3Half((Half)1, (Half)2, (Half)3); + Vector3Half b = new Vector3Half((Half)(-2), (Half)(-3), (Half)(-4)); + Assert.That(a - b, Is.EqualTo(new Vector3Half((Half)3, (Half)5, (Half)7))); + } + + [Test] + public void OperatorInverse() + { + Vector3Half v = new Vector3Half((Half)1, (Half)2, (Half)3); + Assert.That(-v, Is.EqualTo(new Vector3Half((Half)(-1), (Half)(-2), (Half)(-3)))); + } + + // [Test] + // public void OperatorMultiply() + // { + // Vector3Half a = new Vector3Half((Half)1, (Half)2, (Half)3); + // // a * n, n * a are two different operators. test both. + // Assert.That(a * (Half)2, Is.EqualTo(new Vector3Half((Half)2, (Half)4, (Half)6))); + // Assert.That((Half)2 * a, Is.EqualTo(new Vector3Half((Half)2, (Half)4, (Half)6))); + // } + +#if UNITY_2021_3_OR_NEWER + [Test] + public void OperatorEquals() + { + // two vectors which are approximately the same + Vector3Half a = new Vector3Half((Half)1, (Half)2, (Half)3); + Vector3Half b = new Vector3Half((Half)1, (Half)2, (Half)3); + Assert.That(a == b, Is.True); + + // two vectors which are definitely not the same + Assert.That(a == Vector3Half.one, Is.False); + } + + [Test] + public void OperatorNotEquals() + { + // two vectors which are approximately the same + Vector3Half a = new Vector3Half((Half)1, (Half)2, (Half)3); + Vector3Half b = new Vector3Half((Half)1, (Half)2, (Half)3); + Assert.That(a != b, Is.False); + + // two vectors which are definitely not the same + Assert.That(a != Vector3Half.one, Is.True); + } +#endif + + [Test] + public void OperatorIndexer() + { + Vector3Half a = new Vector3Half((Half)1, (Half)2, (Half)3); + + // get + Assert.That(a[0], Is.EqualTo((Half)1)); + Assert.That(a[1], Is.EqualTo((Half)2)); + Assert.That(a[2], Is.EqualTo((Half)3)); + Assert.Throws(() => + { + Half _ = a[-1]; + }); + Assert.Throws(() => + { + Half _ = a[3]; + }); + + // set + a[0] = -1; + a[1] = -2; + a[2] = -3; + Assert.Throws(() => { a[-1] = (Half)0; }); + Assert.Throws(() => { a[3] = (Half)0; }); + Assert.That(a, Is.EqualTo(new Vector3Half((Half)(-1), (Half)(-2), (Half)(-3)))); + } + + [Test] + public void ToStringTest() + { + // should be rounded to :F2 + Vector3Half v = new Vector3Half((Half)(-10), (Half)0, (Half)42); + Assert.That(v.ToString(), Is.EqualTo("(-10 0 42)")); + } + +#if UNITY_2021_3_OR_NEWER + [Test] + public void EqualsVector3Half() + { + Assert.That(Vector3Half.one.Equals(Vector3Half.one), Is.True); + Assert.That(Vector3Half.one.Equals(Vector3Half.zero), Is.False); + } + + [Test] + public void EqualsObject() + { + Assert.That(Vector3Half.one.Equals((object)42), Is.False); + Assert.That(Vector3Half.one.Equals((object)Vector3Half.one), Is.True); + Assert.That(Vector3Half.one.Equals((object)Vector3Half.zero), Is.False); + } + + [Test] + public void GetHashCodeTest() + { + // shouldn't be 0 + Assert.That(Vector3Half.zero.GetHashCode(), !Is.EqualTo(0)); + Assert.That(Vector3Half.one.GetHashCode(), !Is.EqualTo(0)); + + // should be same for same vector + Assert.That(Vector3Half.zero.GetHashCode(), Is.EqualTo(Vector3Half.zero.GetHashCode())); + + // should be different for different vectors + Assert.That(Vector3Half.zero.GetHashCode(), !Is.EqualTo(Vector3Half.one.GetHashCode())); + } +#endif + } +} diff --git a/Assets/Mirror/Tests/Editor/Tools/Vector3HalfTests.cs.meta b/Assets/Mirror/Tests/Editor/Tools/Vector3HalfTests.cs.meta new file mode 100644 index 000000000..d70f401d2 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Tools/Vector3HalfTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bc5f32db47ab4ffb92433050cc1b6e9f +timeCreated: 1730890719 \ No newline at end of file