From f99d4fb1b7e36f8f44c1684927d4de9ccd27b6d7 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Tue, 31 Dec 2024 10:11:08 +0100 Subject: [PATCH] Fixed issues, added test project --- .gitignore | 5 + ControlCore.csproj | 19 +++ LinearAlgebra/Angle.cs | 5 + LinearAlgebra/Quat32.cs | 78 +++++++++++ LinearAlgebra/SwingTwist.cs | 20 ++- LowLevelMessages.cs | 186 +++++++++++++----------- Participant.cs | 32 +++-- Thing.cs | 124 ++++++++++------ float16.cs | 272 ++++++++++++++++++++++++++++++++++++ test.sln | 36 +++++ test/UnitTest1.cs | 17 +++ test/test.csproj | 28 ++++ 12 files changed, 678 insertions(+), 144 deletions(-) create mode 100644 ControlCore.csproj create mode 100644 LinearAlgebra/Angle.cs create mode 100644 float16.cs create mode 100644 test.sln create mode 100644 test/UnitTest1.cs create mode 100644 test/test.csproj diff --git a/.gitignore b/.gitignore index 86d94f4..2ef642b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ DoxyGen/DoxyWarnLogfile.txt +.vscode/settings.json +test/bin +test/obj +/bin +/obj diff --git a/ControlCore.csproj b/ControlCore.csproj new file mode 100644 index 0000000..b63b20c --- /dev/null +++ b/ControlCore.csproj @@ -0,0 +1,19 @@ + + + + false + false + net9.0 + enable + enable + + + + + + + + + + + diff --git a/LinearAlgebra/Angle.cs b/LinearAlgebra/Angle.cs new file mode 100644 index 0000000..6b65a0b --- /dev/null +++ b/LinearAlgebra/Angle.cs @@ -0,0 +1,5 @@ +class Angle +{ + public static float Rad2Deg = 360.0f / ((float)Math.PI * 2); + public static float Deg2Rad = ((float)Math.PI * 2) / 360.0f; +} \ No newline at end of file diff --git a/LinearAlgebra/Quat32.cs b/LinearAlgebra/Quat32.cs index 1f41a99..9680eb0 100644 --- a/LinearAlgebra/Quat32.cs +++ b/LinearAlgebra/Quat32.cs @@ -7,6 +7,14 @@ namespace Passer.LinearAlgebra public float z; public float w; + public Quat32() + { + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 1; + } + public Quat32(float x, float y, float z, float w) { this.x = x; @@ -14,5 +22,75 @@ namespace Passer.LinearAlgebra this.z = z; this.w = w; } + + public static Quat32 FromSwingTwist(SwingTwist s) + { + Quat32 q32 = Quat32.Euler(-s.swing.vertical, s.swing.horizontal, s.twist); + return q32; + } + + public static Quat32 Euler(float yaw, float pitch, float roll) + { + float rollOver2 = roll * Angle.Deg2Rad * 0.5f; + float sinRollOver2 = (float)Math.Sin((float)rollOver2); + float cosRollOver2 = (float)Math.Cos((float)rollOver2); + float pitchOver2 = pitch * 0.5f; + float sinPitchOver2 = (float)Math.Sin((float)pitchOver2); + float cosPitchOver2 = (float)Math.Cos((float)pitchOver2); + float yawOver2 = yaw * 0.5f; + float sinYawOver2 = (float)Math.Sin((float)yawOver2); + float cosYawOver2 = (float)Math.Cos((float)yawOver2); + Quat32 result = new() + { + w = cosYawOver2 * cosPitchOver2 * cosRollOver2 + + sinYawOver2 * sinPitchOver2 * sinRollOver2, + x = sinYawOver2 * cosPitchOver2 * cosRollOver2 + + cosYawOver2 * sinPitchOver2 * sinRollOver2, + y = cosYawOver2 * sinPitchOver2 * cosRollOver2 - + sinYawOver2 * cosPitchOver2 * sinRollOver2, + z = cosYawOver2 * cosPitchOver2 * sinRollOver2 - + sinYawOver2 * sinPitchOver2 * cosRollOver2 + }; + return result; + } + + public void ToAngles(out float right, out float up, out float forward) + { + float test = this.x * this.y + this.z * this.w; + if (test > 0.499f) + { // singularity at north pole + right = 0; + up = 2 * (float)Math.Atan2(this.x, this.w) * Angle.Rad2Deg; + forward = 90; + return; + //return Vector3(0, 2 * (float)atan2(this.x, this.w) * Angle.Rad2Deg, 90); + } + else if (test < -0.499f) + { // singularity at south pole + right = 0; + up = -2 * (float)Math.Atan2(this.x, this.w) * Angle.Rad2Deg; + forward = -90; + return; + //return Vector3(0, -2 * (float)atan2(this.x, this.w) * Angle.Rad2Deg, -90); + } + else + { + float sqx = this.x * this.x; + float sqy = this.y * this.y; + float sqz = this.z * this.z; + + right = (float)Math.Atan2(2 * this.x * this.w - 2 * this.y * this.z, 1 - 2 * sqx - 2 * sqz) * Angle.Rad2Deg; + up = (float)Math.Atan2(2 * this.y * this.w - 2 * this.x * this.z, 1 - 2 * sqy - 2 * sqz) * Angle.Rad2Deg; + forward = (float)Math.Asin(2 * test) * Angle.Rad2Deg; + return; + // return Vector3( + // atan2f(2 * this.x * this.w - 2 * this.y * this.z, 1 - 2 * sqx - 2 * sqz) * + // Rad2Deg, + // atan2f(2 * this.y * this.w - 2 * this.x * this.z, 1 - 2 * sqy - 2 * sqz) * + // Rad2Deg, + // asinf(2 * test) * Angle.Rad2Deg); + } + } + } } \ No newline at end of file diff --git a/LinearAlgebra/SwingTwist.cs b/LinearAlgebra/SwingTwist.cs index c73a28d..61774d1 100644 --- a/LinearAlgebra/SwingTwist.cs +++ b/LinearAlgebra/SwingTwist.cs @@ -1,20 +1,32 @@ -namespace Passer.LinearAlgebra { +namespace Passer.LinearAlgebra +{ - public class SwingTwist { + public class SwingTwist + { public Direction swing; public float twist; public static readonly SwingTwist zero = new(0, 0, 0); - public SwingTwist(Direction swing, float twist) { + public SwingTwist(Direction swing, float twist) + { this.swing = swing; this.twist = twist; } - public SwingTwist(float horizontalSwing, float verticalSwing, float twist) { + public SwingTwist(float horizontalSwing, float verticalSwing, float twist) + { this.swing = new Direction(horizontalSwing, verticalSwing); this.swing.Normalize(); this.twist = twist; } + public static SwingTwist FromQuat32(Quat32 q32) + { + // UnityEngine.Quaternion q = new(q32.x, q32.y, q32.z, q32.w); + // SwingTwist r = new(q.eulerAngles.y, q.eulerAngles.x, q.eulerAngles.z); + q32.ToAngles(out float right, out float up, out float forward); + SwingTwist r = new(up, right, forward); + return r; + } } } \ No newline at end of file diff --git a/LowLevelMessages.cs b/LowLevelMessages.cs index 051b5f5..055096b 100644 --- a/LowLevelMessages.cs +++ b/LowLevelMessages.cs @@ -1,91 +1,109 @@ using Passer.LinearAlgebra; -public class LowLevelMessages { +namespace Passer.Control.Core +{ + public class LowLevelMessages + { - public static void SendSpherical(byte[] buffer, ref byte ix, Spherical v) { - SendFloat16(buffer, ref ix, new float16(v.distance)); - SendAngle8(buffer, ref ix, v.direction.horizontal); - SendAngle8(buffer, ref ix, v.direction.horizontal); - } - - public static Spherical ReceiveSpherical(byte[] data, ref byte ix) { - float distance = ReceiveFloat16(data, ref ix); - float horizontal = ReceiveAngle8(data, ref ix); - float vertical = ReceiveAngle8(data, ref ix); - Spherical v = new(distance, horizontal, vertical); - return v; - } - - public static void SendQuat32(byte[] buffer, ref byte ix, SwingTwist s) { - UnityEngine.Quaternion q = UnityEngine.Quaternion.Euler(-s.swing.vertical, s.swing.horizontal, s.twist); - Quat32 q32 = new(q.x, q.y, q.z, q.w); - SendQuat32(buffer, ref ix, q32); - - } - public static void SendQuat32(byte[] buffer, ref byte ix, Quat32 q) { - int qx = (int)(q.x * 127 + 128); - int qy = (int)(q.y * 127 + 128); - int qz = (int)(q.z * 127 + 128); - int qw = (int)(q.w * 255); - if (q.w < 0) { - qx = -qx; - qy = -qy; - qz = -qz; - qw = -qw; + public static void SendSpherical(byte[] buffer, ref byte ix, Spherical v) + { + SendFloat16(buffer, ref ix, new float16(v.distance)); + SendAngle8(buffer, ref ix, v.direction.horizontal); + SendAngle8(buffer, ref ix, v.direction.horizontal); } - buffer[ix++] = (byte)qx; - buffer[ix++] = (byte)qy; - buffer[ix++] = (byte)qz; - buffer[ix++] = (byte)qw; - } - public static Quat32 ReceiveQuat32(byte[] data, ref byte ix) { - Quat32 q = new( - (data[ix++] - 128.0F) / 127.0F, - (data[ix++] - 128.0F) / 127.0F, - (data[ix++] - 128.0F) / 127.0F, - data[ix++] / 255.0F); - return q; + public static Spherical ReceiveSpherical(byte[] data, ref byte ix) + { + float distance = ReceiveFloat16(data, ref ix); + float horizontal = ReceiveAngle8(data, ref ix); + float vertical = ReceiveAngle8(data, ref ix); + Spherical v = new(distance, horizontal, vertical); + return v; + } + + public static void SendQuat32(byte[] buffer, ref byte ix, SwingTwist s) + { + Quat32 q32 = Quat32.FromSwingTwist(s); + SendQuat32(buffer, ref ix, q32); + + } + public static void SendQuat32(byte[] buffer, ref byte ix, Quat32 q) + { + int qx = (int)(q.x * 127 + 128); + int qy = (int)(q.y * 127 + 128); + int qz = (int)(q.z * 127 + 128); + int qw = (int)(q.w * 255); + if (q.w < 0) + { + qx = -qx; + qy = -qy; + qz = -qz; + qw = -qw; + } + + buffer[ix++] = (byte)qx; + buffer[ix++] = (byte)qy; + buffer[ix++] = (byte)qz; + buffer[ix++] = (byte)qw; + } + public static Quat32 ReceiveQuat32(byte[] data, ref byte ix) + { + Quat32 q = new( + (data[ix++] - 128.0F) / 127.0F, + (data[ix++] - 128.0F) / 127.0F, + (data[ix++] - 128.0F) / 127.0F, + data[ix++] / 255.0F); + return q; + } + + public static SwingTwist ReceiveSwingTwist(byte[] data, ref byte ix) + { + Quat32 q32 = ReceiveQuat32(data, ref ix); + // UnityEngine.Quaternion q = new(q32.x, q32.y, q32.z, q32.w); + // SwingTwist r = new(q.eulerAngles.y, q.eulerAngles.x, q.eulerAngles.z); + + SwingTwist r = SwingTwist.FromQuat32(q32); + return r; + } + + public static void SendAngle8(byte[] buffer, ref byte ix, float angle) + { + // Normalize angle + while (angle >= 180) + angle -= 360; + while (angle < -180) + angle += 360; + buffer[ix++] = (byte)((angle / 360.0f) * 256.0f); + } + + public static float ReceiveAngle8(byte[] data, ref byte ix) + { + float value = (data[ix++] * 180) / 128.0F; + return value; + } + + public static void SendFloat16(byte[] data, ref byte ix, float f) + { + float16 f16 = new(f); + ushort binary = f16.GetBinary(); + data[ix++] = (byte)(binary >> 8); + data[ix++] = (byte)(binary & 255); + } + public static void SendFloat16(byte[] data, ref byte ix, float16 f) + { + ushort binary = f.GetBinary(); + data[ix++] = (byte)(binary >> 8); + data[ix++] = (byte)(binary & 255); + } + + public static float ReceiveFloat16(byte[] data, ref byte ix) + { + ushort value = (ushort)(data[ix++] << 8 | data[ix++]); + float16 f16 = new(); + f16.SetBinary(value); + float f = f16.toFloat(); + return f; + } } - public static SwingTwist ReceiveSwingTwist(byte[] data, ref byte ix) { - Quat32 q32 = ReceiveQuat32(data, ref ix); - UnityEngine.Quaternion q = new(q32.x, q32.y, q32.z, q32.w); - SwingTwist r = new(q.eulerAngles.y, q.eulerAngles.x, q.eulerAngles.z); - return r; - } - - public static void SendAngle8(byte[] buffer, ref byte ix, float angle) { - // Normalize angle - while (angle >= 180) - angle -= 360; - while (angle < -180) - angle += 360; - buffer[ix++] = (byte)((angle / 360.0f) * 256.0f); - } - - public static float ReceiveAngle8(byte[] data, ref byte ix) { - float value = (data[ix++] * 180) / 128.0F; - return value; - } - - public static void SendFloat16(byte[] data, ref byte ix, float f) { - float16 f16 = new(f); - ushort binary = f16.GetBinary(); - data[ix++] = (byte)(binary >> 8); - data[ix++] = (byte)(binary & 255); - } - public static void SendFloat16(byte[] data, ref byte ix, float16 f) { - ushort binary = f.GetBinary(); - data[ix++] = (byte)(binary >> 8); - data[ix++] = (byte)(binary & 255); - } - - public static float ReceiveFloat16(byte[] data, ref byte ix) { - ushort value = (ushort)(data[ix++] << 8 | data[ix++]); - float16 f16 = new(); - f16.SetBinary(value); - float f = f16.toFloat(); - return f; - } -} +} \ No newline at end of file diff --git a/Participant.cs b/Participant.cs index 147b05f..3e71974 100644 --- a/Participant.cs +++ b/Participant.cs @@ -37,7 +37,8 @@ namespace Passer.Control.Core #region Init - public Participant() { + public Participant() + { this.dataStream = new EchoStream(); others.Add(this); } @@ -59,14 +60,17 @@ namespace Passer.Control.Core private float nextPublishMe = 0; - public virtual void Update(float currentTime) { - if (currentTime > this.nextPublishMe) { + public virtual void Update(float currentTime) + { + if (currentTime > this.nextPublishMe) + { this.PublishBuffer(ClientMsg.Serialized(this.buffer, this.networkId)); ClientMsg.Publish(this, this.networkId); this.nextPublishMe = currentTime + Participant.publishInterval; } - for (int ix = 0; ix < this.others.Count; ix++) { + for (int ix = 0; ix < this.others.Count; ix++) + { Participant client = this.others[ix]; if (client == null) continue; @@ -85,7 +89,7 @@ namespace Passer.Control.Core //if (this.ipAddress == null) // return false; - UnityEngine.Debug.Log($"Send msg {buffer[0]} to {ipAddress}"); + // UnityEngine.Debug.Log($"Send msg {buffer[0]} to {ipAddress}"); //this.udpClient.Send(this.buffer, bufferSize, this.ipAddress, this.port); this.udpClient.Send(this.buffer, bufferSize, this.endPoint); return true; @@ -104,8 +108,10 @@ namespace Passer.Control.Core #region Receive - public async Task ReceiveData() { - while (true) { + public async Task ReceiveData() + { + while (true) + { byte packetSize = (byte)this.dataStream.ReadByte(); if (packetSize != 0xFF) await ReceiveData(this.dataStream, this, packetSize); @@ -113,16 +119,19 @@ namespace Passer.Control.Core } } - public static async Task ReceiveData(Stream dataStream, Participant client, byte packetSize) { + public static async Task ReceiveData(Stream dataStream, Participant client, byte packetSize) + { byte msgId = (byte)dataStream.ReadByte(); - if (msgId == 0xFF) { + if (msgId == 0xFF) + { // Timeout return; } //UnityEngine.Debug.Log($"R {msgId} from {client.ipAddress}"); bool result = false; - switch (msgId) { + switch (msgId) + { case ClientMsg.Id: // 0xA0 / 160 result = await ClientMsg.Receive(dataStream, client, packetSize); break; @@ -156,7 +165,8 @@ namespace Passer.Control.Core default: break; } - if (result == false) { + if (result == false) + { packetSize = msgId; // skip 1 byte, msgId is possibly a packet size byte await ReceiveData(dataStream, client, packetSize); } diff --git a/Thing.cs b/Thing.cs index 575f4e8..4e70207 100644 --- a/Thing.cs +++ b/Thing.cs @@ -1,7 +1,8 @@ using System.Collections.Generic; using Passer.LinearAlgebra; -namespace Passer.Control.Core { +namespace Passer.Control.Core +{ /// /// A thing is the basic building block @@ -18,29 +19,36 @@ namespace Passer.Control.Core { public event ChangeHandler OnParentChanged; private Thing _parent; - public Thing parent { + public Thing parent + { get => _parent; - set { + set + { if (_parent == value) return; - if (value == null) { + if (value == null) + { _parent.RemoveChild(this); _parent = null; - } else { + } + else + { value.AddChild(this); OnParentChanged?.Invoke(); } } } - public void AddChild(Thing child) { + public void AddChild(Thing child) + { if (children.Find(thing => thing == child) != null) return; child._parent = this; children.Add(child); } - public void RemoveChild(Thing child) { + public void RemoveChild(Thing child) + { children.Remove(child); } @@ -50,10 +58,13 @@ namespace Passer.Control.Core { public event ChangeHandler OnNameChanged; private string _name; - public string name { + public string name + { get => _name; - set { - if (_name != value) { + set + { + if (_name != value) + { _name = value; OnNameChanged?.Invoke(); } @@ -65,10 +76,13 @@ namespace Passer.Control.Core { public byte poseUpdated = 0x00; public event ChangeHandler OnPositionChanged; private Spherical _position; - public Spherical position { + public Spherical position + { get { return _position; } - set { - if (_position != value) { + set + { + if (_position != value) + { _position = value; OnPositionChanged?.Invoke(); } @@ -77,10 +91,13 @@ namespace Passer.Control.Core { public event ChangeHandler OnOrientationChanged; private SwingTwist _orientation; - public SwingTwist orientation { + public SwingTwist orientation + { get { return _orientation; } - set { - if (_orientation != value) { + set + { + if (_orientation != value) + { _orientation = value; OnOrientationChanged?.Invoke(); } @@ -89,10 +106,13 @@ namespace Passer.Control.Core { public event SphericalHandler OnLinearVelocityChanged; private Spherical _linearVelocity; - public Spherical linearVelocity { + public Spherical linearVelocity + { get => _linearVelocity; - set { - if (_linearVelocity != value) { + set + { + if (_linearVelocity != value) + { _linearVelocity = value; OnLinearVelocityChanged?.Invoke(_linearVelocity); } @@ -100,17 +120,21 @@ namespace Passer.Control.Core { } public Spherical angularVelocity; - public virtual void Init(bool invokeEvent = true) { + public virtual void Init(bool invokeEvent = true) + { Thing.Add(this, invokeEvent); } - public Thing(bool initialize = true) { - if (initialize) { + public Thing(bool initialize = true) + { + if (initialize) + { //this.Init(); Thing.Add(this); } } - public Thing(Participant client, byte networkId, byte thingId, byte thingType = 0) { + public Thing(Participant client, byte networkId, byte thingId, byte thingType = 0) + { this.participant = client; this.id = thingId; this.type = thingType; @@ -119,29 +143,31 @@ namespace Passer.Control.Core { Thing.Add(this); } - public virtual void Update(float currentTime) { + public virtual void Update(float currentTime) + { // should recurse over children... } - public virtual void ProcessBytes(byte[] bytes) { + public virtual void ProcessBytes(byte[] bytes) + { //if (sensor != null) // sensor.ProcessBytes(bytes); } // Experimental - public float stressLevel = 0; + // public float stressLevel = 0; - protected delegate void ReceptorFunc(Sensor sensor); - protected void SetupReceptor(Sensor sensor, ReceptorFunc receptor) { - sensor.Signaller += (sensor => Receptor(receptor, sensor)); - } - protected void Receptor(ReceptorFunc receptor, Sensor sensor) { - if (sensor.signalStrength <= stressLevel) - return; + // protected delegate void ReceptorFunc(Sensor sensor); + // protected void SetupReceptor(Sensor sensor, ReceptorFunc receptor) { + // sensor.Signaller += (sensor => Receptor(receptor, sensor)); + // } + // protected void Receptor(ReceptorFunc receptor, Sensor sensor) { + // if (sensor.signalStrength <= stressLevel) + // return; - receptor(sensor); - } + // receptor(sensor); + // } //---------- All Things @@ -150,40 +176,48 @@ namespace Passer.Control.Core { public delegate void ThingHandler(Thing t); public static event ThingHandler OnNewThing; - public static void Add(Thing thing, bool invokeEvent = true) { + public static void Add(Thing thing, bool invokeEvent = true) + { Thing foundThing = Get(thing.networkId, thing.id); - if (foundThing == null) { + if (foundThing == null) + { if (thing.id == 0) thing.id = (byte)(allThings.Count + 1); allThings.Add(thing); if (invokeEvent) OnNewThing?.Invoke(thing); - UnityEngine.Debug.Log($"Add thing [{thing.networkId}/{thing.id}] {thing.name}"); + // UnityEngine.Debug.Log($"Add thing [{thing.networkId}/{thing.id}] {thing.name}"); } } - public static Thing Get(byte networkId, byte thingId) { + public static Thing Get(byte networkId, byte thingId) + { Thing thing = allThings.Find(aThing => IsThing(aThing, networkId, thingId)); return thing; } - private static bool IsThing(Thing thing, byte networkId, byte thingId) { + private static bool IsThing(Thing thing, byte networkId, byte thingId) + { if (thing == null) return false; - return (thing.networkId == networkId ) && (thing.id == thingId); + return (thing.networkId == networkId) && (thing.id == thingId); } - public static void Remove(byte networkId, byte thingId) { + public static void Remove(byte networkId, byte thingId) + { allThings.RemoveAll(t => t.networkId == networkId && t.id == thingId); } - public static Thing[] GetAllThings() { + public static Thing[] GetAllThings() + { return allThings.ToArray(); } - public static void UpdateAll(float currentTime) { - foreach (Thing thing in allThings) { + public static void UpdateAll(float currentTime) + { + foreach (Thing thing in allThings) + { if (thing.parent == null) // update only root things thing.Update(currentTime); } diff --git a/float16.cs b/float16.cs new file mode 100644 index 0000000..579cb97 --- /dev/null +++ b/float16.cs @@ -0,0 +1,272 @@ +namespace Passer.LinearAlgebra +{ + + public class float16 + { + // + // FILE: float16.cpp + // AUTHOR: Rob Tillaart + // VERSION: 0.1.8 + // PURPOSE: library for Float16s for Arduino + // URL: http://en.wikipedia.org/wiki/Half-precision_floating-point_format + + ushort _value; + + public float16() { _value = 0; } + + public float16(float f) + { + _value = f32tof16(f); + } + + public float toFloat() + { + return f16tof32(_value); + } + + public ushort GetBinary() { return _value; } + public void SetBinary(ushort value) { _value = value; } + + ////////////////////////////////////////////////////////// + // + // EQUALITIES + // + /* + bool float16::operator ==(const float16 &f) { return (_value == f._value); } + + bool float16::operator !=(const float16 &f) { return (_value != f._value); } + + bool float16::operator >(const float16 &f) { + if ((_value & 0x8000) && (f._value & 0x8000)) + return _value < f._value; + if (_value & 0x8000) + return false; + if (f._value & 0x8000) + return true; + return _value > f._value; + } + + bool float16::operator >=(const float16 &f) { + if ((_value & 0x8000) && (f._value & 0x8000)) + return _value <= f._value; + if (_value & 0x8000) + return false; + if (f._value & 0x8000) + return true; + return _value >= f._value; + } + + bool float16::operator <(const float16 &f) { + if ((_value & 0x8000) && (f._value & 0x8000)) + return _value > f._value; + if (_value & 0x8000) + return true; + if (f._value & 0x8000) + return false; + return _value < f._value; + } + + bool float16::operator <=(const float16 &f) { + if ((_value & 0x8000) && (f._value & 0x8000)) + return _value >= f._value; + if (_value & 0x8000) + return true; + if (f._value & 0x8000) + return false; + return _value <= f._value; + } + + ////////////////////////////////////////////////////////// + // + // NEGATION + // + float16 float16::operator -() { + float16 f16; + f16.setBinary(_value ^ 0x8000); + return f16; + } + + ////////////////////////////////////////////////////////// + // + // MATH + // + float16 float16::operator +(const float16 &f) { + return float16(this->toDouble() + f.toDouble()); + } + + float16 float16::operator -(const float16 &f) { + return float16(this->toDouble() - f.toDouble()); + } + + float16 float16::operator *(const float16 &f) { + return float16(this->toDouble() * f.toDouble()); + } + + float16 float16::operator /(const float16 &f) { + return float16(this->toDouble() / f.toDouble()); + } + + float16 & float16::operator+=(const float16 &f) { + *this = this->toDouble() + f.toDouble(); + return *this; + } + + float16 & float16::operator-=(const float16 &f) { + *this = this->toDouble() - f.toDouble(); + return *this; + } + + float16 & float16::operator*=(const float16 &f) { + *this = this->toDouble() * f.toDouble(); + return *this; + } + + float16 & float16::operator/=(const float16 &f) { + *this = this->toDouble() / f.toDouble(); + return *this; + } + + ////////////////////////////////////////////////////////// + // + // MATH HELPER FUNCTIONS + // + int float16::sign() { + if (_value & 0x8000) + return -1; + if (_value & 0xFFFF) + return 1; + return 0; + } + + bool float16::isZero() { return ((_value & 0x7FFF) == 0x0000); } + + bool float16::isNaN() { + if ((_value & 0x7C00) != 0x7C00) + return false; + if ((_value & 0x03FF) == 0x0000) + return false; + return true; + } + + bool float16::isInf() { return ((_value == 0x7C00) || (_value == 0xFC00)); } + */ + ////////////////////////////////////////////////////////// + // + // CORE CONVERSION + // + float f16tof32(ushort _value) + { + //ushort sgn; + ushort man; + int exp; + float f; + + //Debug.Log($"{_value}"); + + bool sgn = (_value & 0x8000) > 0; + exp = (_value & 0x7C00) >> 10; + man = (ushort)(_value & 0x03FF); + + //Debug.Log($"{sgn} {exp} {man}"); + + // ZERO + if ((_value & 0x7FFF) == 0) + { + return sgn ? -0 : 0; + } + // NAN & INF + if (exp == 0x001F) + { + if (man == 0) + return sgn ? float.NegativeInfinity : float.PositiveInfinity; //-INFINITY : INFINITY; + else + return float.NaN; // NAN; + } + + // SUBNORMAL/NORMAL + if (exp == 0) + f = 0; + else + f = 1; + + // PROCESS MANTISSE + for (int i = 9; i >= 0; i--) + { + f *= 2; + if ((man & (1 << i)) != 0) + f = f + 1; + } + //Debug.Log($"{f}"); + f = f * (float)Math.Pow(2.0f, exp - 25); + if (exp == 0) + { + f = f * (float)Math.Pow(2.0f, -13); // 5.96046447754e-8; + } + //Debug.Log($"{f}"); + return sgn ? -f : f; + } + + ushort f32tof16(float f) + { + //uint t = *(uint*)&f; + uint t = (uint)BitConverter.SingleToInt32Bits(f); + // man bits = 10; but we keep 11 for rounding + ushort man = (ushort)((t & 0x007FFFFF) >> 12); + short exp = (short)((t & 0x7F800000) >> 23); + bool sgn = (t & 0x80000000) != 0; + + // handle 0 + if ((t & 0x7FFFFFFF) == 0) + { + return sgn ? (ushort)0x8000 : (ushort)0x0000; + } + // denormalized float32 does not fit in float16 + if (exp == 0x00) + { + return sgn ? (ushort)0x8000 : (ushort)0x0000; + } + // handle infinity & NAN + if (exp == 0x00FF) + { + if (man != 0) + return 0xFE00; // NAN + return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF + } + + // normal numbers + exp = (short)(exp - 127 + 15); + // overflow does not fit => INF + if (exp > 30) + { + return sgn ? (ushort)0xFC00 : (ushort)0x7C00; // -INF : INF + } + // subnormal numbers + if (exp < -38) + { + return sgn ? (ushort)0x8000 : (ushort)0x0000; // -0 or 0 ? just 0 ? + } + if (exp <= 0) // subnormal + { + man >>= (exp + 14); + // rounding + man++; + man >>= 1; + if (sgn) + return (ushort)(0x8000 | man); + return man; + } + + // normal + // TODO rounding + exp <<= 10; + man++; + man >>= 1; + if (sgn) + return (ushort)(0x8000 | exp | man); + return (ushort)(exp | man); + } + + // -- END OF FILE -- + } + +} \ No newline at end of file diff --git a/test.sln b/test.sln new file mode 100644 index 0000000..5ab05c5 --- /dev/null +++ b/test.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCore", "ControlCore.csproj", "{F2DEE6B0-AF41-454E-AAC8-9E1E3ACC769F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test\test.csproj", "{2C350E2B-9DDA-4037-BAE5-E12AB7A52398}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6601071A-9E9C-42E1-95EA-0A36C5D718E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6601071A-9E9C-42E1-95EA-0A36C5D718E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6601071A-9E9C-42E1-95EA-0A36C5D718E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6601071A-9E9C-42E1-95EA-0A36C5D718E4}.Release|Any CPU.Build.0 = Release|Any CPU + {DD7238DA-DBEF-4D6A-98DB-6FE28BBDB75D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD7238DA-DBEF-4D6A-98DB-6FE28BBDB75D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD7238DA-DBEF-4D6A-98DB-6FE28BBDB75D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD7238DA-DBEF-4D6A-98DB-6FE28BBDB75D}.Release|Any CPU.Build.0 = Release|Any CPU + {F2DEE6B0-AF41-454E-AAC8-9E1E3ACC769F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2DEE6B0-AF41-454E-AAC8-9E1E3ACC769F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2DEE6B0-AF41-454E-AAC8-9E1E3ACC769F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2DEE6B0-AF41-454E-AAC8-9E1E3ACC769F}.Release|Any CPU.Build.0 = Release|Any CPU + {2C350E2B-9DDA-4037-BAE5-E12AB7A52398}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C350E2B-9DDA-4037-BAE5-E12AB7A52398}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C350E2B-9DDA-4037-BAE5-E12AB7A52398}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C350E2B-9DDA-4037-BAE5-E12AB7A52398}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/test/UnitTest1.cs b/test/UnitTest1.cs new file mode 100644 index 0000000..c4b42fd --- /dev/null +++ b/test/UnitTest1.cs @@ -0,0 +1,17 @@ +namespace test; + +using Passer.Control.Core; + +public class Tests +{ + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + Assert.Pass(); + } +} diff --git a/test/test.csproj b/test/test.csproj new file mode 100644 index 0000000..6da79b7 --- /dev/null +++ b/test/test.csproj @@ -0,0 +1,28 @@ + + + + net9.0 + latest + enable + enable + false + true + + + + + + + + + + + + + + + + + + +