Fixed issues, added test project

This commit is contained in:
Pascal Serrarens 2024-12-31 10:11:08 +01:00
parent dd2cbf1646
commit f99d4fb1b7
12 changed files with 678 additions and 144 deletions

5
.gitignore vendored
View File

@ -1 +1,6 @@
DoxyGen/DoxyWarnLogfile.txt DoxyGen/DoxyWarnLogfile.txt
.vscode/settings.json
test/bin
test/obj
/bin
/obj

19
ControlCore.csproj Normal file
View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.3.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
</ItemGroup>
</Project>

5
LinearAlgebra/Angle.cs Normal file
View File

@ -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;
}

View File

@ -7,6 +7,14 @@ namespace Passer.LinearAlgebra
public float z; public float z;
public float w; 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) public Quat32(float x, float y, float z, float w)
{ {
this.x = x; this.x = x;
@ -14,5 +22,75 @@ namespace Passer.LinearAlgebra
this.z = z; this.z = z;
this.w = w; 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);
}
}
} }
} }

View File

@ -1,20 +1,32 @@
namespace Passer.LinearAlgebra { namespace Passer.LinearAlgebra
{
public class SwingTwist { public class SwingTwist
{
public Direction swing; public Direction swing;
public float twist; public float twist;
public static readonly SwingTwist zero = new(0, 0, 0); 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.swing = swing;
this.twist = twist; 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 = new Direction(horizontalSwing, verticalSwing);
this.swing.Normalize(); this.swing.Normalize();
this.twist = twist; 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;
}
} }
} }

View File

@ -1,14 +1,19 @@
using Passer.LinearAlgebra; using Passer.LinearAlgebra;
public class LowLevelMessages { namespace Passer.Control.Core
{
public class LowLevelMessages
{
public static void SendSpherical(byte[] buffer, ref byte ix, Spherical v) { public static void SendSpherical(byte[] buffer, ref byte ix, Spherical v)
{
SendFloat16(buffer, ref ix, new float16(v.distance)); SendFloat16(buffer, ref ix, new float16(v.distance));
SendAngle8(buffer, ref ix, v.direction.horizontal); SendAngle8(buffer, ref ix, v.direction.horizontal);
SendAngle8(buffer, ref ix, v.direction.horizontal); SendAngle8(buffer, ref ix, v.direction.horizontal);
} }
public static Spherical ReceiveSpherical(byte[] data, ref byte ix) { public static Spherical ReceiveSpherical(byte[] data, ref byte ix)
{
float distance = ReceiveFloat16(data, ref ix); float distance = ReceiveFloat16(data, ref ix);
float horizontal = ReceiveAngle8(data, ref ix); float horizontal = ReceiveAngle8(data, ref ix);
float vertical = ReceiveAngle8(data, ref ix); float vertical = ReceiveAngle8(data, ref ix);
@ -16,18 +21,20 @@ public class LowLevelMessages {
return v; return v;
} }
public static void SendQuat32(byte[] buffer, ref byte ix, SwingTwist s) { 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); Quat32 q32 = Quat32.FromSwingTwist(s);
SendQuat32(buffer, ref ix, q32); SendQuat32(buffer, ref ix, q32);
} }
public static void SendQuat32(byte[] buffer, ref byte ix, Quat32 q) { public static void SendQuat32(byte[] buffer, ref byte ix, Quat32 q)
{
int qx = (int)(q.x * 127 + 128); int qx = (int)(q.x * 127 + 128);
int qy = (int)(q.y * 127 + 128); int qy = (int)(q.y * 127 + 128);
int qz = (int)(q.z * 127 + 128); int qz = (int)(q.z * 127 + 128);
int qw = (int)(q.w * 255); int qw = (int)(q.w * 255);
if (q.w < 0) { if (q.w < 0)
{
qx = -qx; qx = -qx;
qy = -qy; qy = -qy;
qz = -qz; qz = -qz;
@ -39,7 +46,8 @@ public class LowLevelMessages {
buffer[ix++] = (byte)qz; buffer[ix++] = (byte)qz;
buffer[ix++] = (byte)qw; buffer[ix++] = (byte)qw;
} }
public static Quat32 ReceiveQuat32(byte[] data, ref byte ix) { public static Quat32 ReceiveQuat32(byte[] data, ref byte ix)
{
Quat32 q = new( Quat32 q = new(
(data[ix++] - 128.0F) / 127.0F, (data[ix++] - 128.0F) / 127.0F,
(data[ix++] - 128.0F) / 127.0F, (data[ix++] - 128.0F) / 127.0F,
@ -48,14 +56,18 @@ public class LowLevelMessages {
return q; return q;
} }
public static SwingTwist ReceiveSwingTwist(byte[] data, ref byte ix) { public static SwingTwist ReceiveSwingTwist(byte[] data, ref byte ix)
{
Quat32 q32 = ReceiveQuat32(data, ref ix); Quat32 q32 = ReceiveQuat32(data, ref ix);
UnityEngine.Quaternion q = new(q32.x, q32.y, q32.z, q32.w); // 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 = new(q.eulerAngles.y, q.eulerAngles.x, q.eulerAngles.z);
SwingTwist r = SwingTwist.FromQuat32(q32);
return r; return r;
} }
public static void SendAngle8(byte[] buffer, ref byte ix, float angle) { public static void SendAngle8(byte[] buffer, ref byte ix, float angle)
{
// Normalize angle // Normalize angle
while (angle >= 180) while (angle >= 180)
angle -= 360; angle -= 360;
@ -64,24 +76,28 @@ public class LowLevelMessages {
buffer[ix++] = (byte)((angle / 360.0f) * 256.0f); buffer[ix++] = (byte)((angle / 360.0f) * 256.0f);
} }
public static float ReceiveAngle8(byte[] data, ref byte ix) { public static float ReceiveAngle8(byte[] data, ref byte ix)
{
float value = (data[ix++] * 180) / 128.0F; float value = (data[ix++] * 180) / 128.0F;
return value; return value;
} }
public static void SendFloat16(byte[] data, ref byte ix, float f) { public static void SendFloat16(byte[] data, ref byte ix, float f)
{
float16 f16 = new(f); float16 f16 = new(f);
ushort binary = f16.GetBinary(); ushort binary = f16.GetBinary();
data[ix++] = (byte)(binary >> 8); data[ix++] = (byte)(binary >> 8);
data[ix++] = (byte)(binary & 255); data[ix++] = (byte)(binary & 255);
} }
public static void SendFloat16(byte[] data, ref byte ix, float16 f) { public static void SendFloat16(byte[] data, ref byte ix, float16 f)
{
ushort binary = f.GetBinary(); ushort binary = f.GetBinary();
data[ix++] = (byte)(binary >> 8); data[ix++] = (byte)(binary >> 8);
data[ix++] = (byte)(binary & 255); data[ix++] = (byte)(binary & 255);
} }
public static float ReceiveFloat16(byte[] data, ref byte ix) { public static float ReceiveFloat16(byte[] data, ref byte ix)
{
ushort value = (ushort)(data[ix++] << 8 | data[ix++]); ushort value = (ushort)(data[ix++] << 8 | data[ix++]);
float16 f16 = new(); float16 f16 = new();
f16.SetBinary(value); f16.SetBinary(value);
@ -89,3 +105,5 @@ public class LowLevelMessages {
return f; return f;
} }
} }
}

View File

@ -37,7 +37,8 @@ namespace Passer.Control.Core
#region Init #region Init
public Participant() { public Participant()
{
this.dataStream = new EchoStream(); this.dataStream = new EchoStream();
others.Add(this); others.Add(this);
} }
@ -59,14 +60,17 @@ namespace Passer.Control.Core
private float nextPublishMe = 0; private float nextPublishMe = 0;
public virtual void Update(float currentTime) { public virtual void Update(float currentTime)
if (currentTime > this.nextPublishMe) { {
if (currentTime > this.nextPublishMe)
{
this.PublishBuffer(ClientMsg.Serialized(this.buffer, this.networkId)); this.PublishBuffer(ClientMsg.Serialized(this.buffer, this.networkId));
ClientMsg.Publish(this, this.networkId); ClientMsg.Publish(this, this.networkId);
this.nextPublishMe = currentTime + Participant.publishInterval; 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]; Participant client = this.others[ix];
if (client == null) if (client == null)
continue; continue;
@ -85,7 +89,7 @@ namespace Passer.Control.Core
//if (this.ipAddress == null) //if (this.ipAddress == null)
// return false; // 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.ipAddress, this.port);
this.udpClient.Send(this.buffer, bufferSize, this.endPoint); this.udpClient.Send(this.buffer, bufferSize, this.endPoint);
return true; return true;
@ -104,8 +108,10 @@ namespace Passer.Control.Core
#region Receive #region Receive
public async Task ReceiveData() { public async Task ReceiveData()
while (true) { {
while (true)
{
byte packetSize = (byte)this.dataStream.ReadByte(); byte packetSize = (byte)this.dataStream.ReadByte();
if (packetSize != 0xFF) if (packetSize != 0xFF)
await ReceiveData(this.dataStream, this, packetSize); 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(); byte msgId = (byte)dataStream.ReadByte();
if (msgId == 0xFF) { if (msgId == 0xFF)
{
// Timeout // Timeout
return; return;
} }
//UnityEngine.Debug.Log($"R {msgId} from {client.ipAddress}"); //UnityEngine.Debug.Log($"R {msgId} from {client.ipAddress}");
bool result = false; bool result = false;
switch (msgId) { switch (msgId)
{
case ClientMsg.Id: // 0xA0 / 160 case ClientMsg.Id: // 0xA0 / 160
result = await ClientMsg.Receive(dataStream, client, packetSize); result = await ClientMsg.Receive(dataStream, client, packetSize);
break; break;
@ -156,7 +165,8 @@ namespace Passer.Control.Core
default: default:
break; break;
} }
if (result == false) { if (result == false)
{
packetSize = msgId; // skip 1 byte, msgId is possibly a packet size byte packetSize = msgId; // skip 1 byte, msgId is possibly a packet size byte
await ReceiveData(dataStream, client, packetSize); await ReceiveData(dataStream, client, packetSize);
} }

122
Thing.cs
View File

@ -1,7 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using Passer.LinearAlgebra; using Passer.LinearAlgebra;
namespace Passer.Control.Core { namespace Passer.Control.Core
{
/// <summary> /// <summary>
/// A thing is the basic building block /// A thing is the basic building block
@ -18,29 +19,36 @@ namespace Passer.Control.Core {
public event ChangeHandler OnParentChanged; public event ChangeHandler OnParentChanged;
private Thing _parent; private Thing _parent;
public Thing parent { public Thing parent
{
get => _parent; get => _parent;
set { set
{
if (_parent == value) if (_parent == value)
return; return;
if (value == null) { if (value == null)
{
_parent.RemoveChild(this); _parent.RemoveChild(this);
_parent = null; _parent = null;
} else { }
else
{
value.AddChild(this); value.AddChild(this);
OnParentChanged?.Invoke(); OnParentChanged?.Invoke();
} }
} }
} }
public void AddChild(Thing child) { public void AddChild(Thing child)
{
if (children.Find(thing => thing == child) != null) if (children.Find(thing => thing == child) != null)
return; return;
child._parent = this; child._parent = this;
children.Add(child); children.Add(child);
} }
public void RemoveChild(Thing child) { public void RemoveChild(Thing child)
{
children.Remove(child); children.Remove(child);
} }
@ -50,10 +58,13 @@ namespace Passer.Control.Core {
public event ChangeHandler OnNameChanged; public event ChangeHandler OnNameChanged;
private string _name; private string _name;
public string name { public string name
{
get => _name; get => _name;
set { set
if (_name != value) { {
if (_name != value)
{
_name = value; _name = value;
OnNameChanged?.Invoke(); OnNameChanged?.Invoke();
} }
@ -65,10 +76,13 @@ namespace Passer.Control.Core {
public byte poseUpdated = 0x00; public byte poseUpdated = 0x00;
public event ChangeHandler OnPositionChanged; public event ChangeHandler OnPositionChanged;
private Spherical _position; private Spherical _position;
public Spherical position { public Spherical position
{
get { return _position; } get { return _position; }
set { set
if (_position != value) { {
if (_position != value)
{
_position = value; _position = value;
OnPositionChanged?.Invoke(); OnPositionChanged?.Invoke();
} }
@ -77,10 +91,13 @@ namespace Passer.Control.Core {
public event ChangeHandler OnOrientationChanged; public event ChangeHandler OnOrientationChanged;
private SwingTwist _orientation; private SwingTwist _orientation;
public SwingTwist orientation { public SwingTwist orientation
{
get { return _orientation; } get { return _orientation; }
set { set
if (_orientation != value) { {
if (_orientation != value)
{
_orientation = value; _orientation = value;
OnOrientationChanged?.Invoke(); OnOrientationChanged?.Invoke();
} }
@ -89,10 +106,13 @@ namespace Passer.Control.Core {
public event SphericalHandler OnLinearVelocityChanged; public event SphericalHandler OnLinearVelocityChanged;
private Spherical _linearVelocity; private Spherical _linearVelocity;
public Spherical linearVelocity { public Spherical linearVelocity
{
get => _linearVelocity; get => _linearVelocity;
set { set
if (_linearVelocity != value) { {
if (_linearVelocity != value)
{
_linearVelocity = value; _linearVelocity = value;
OnLinearVelocityChanged?.Invoke(_linearVelocity); OnLinearVelocityChanged?.Invoke(_linearVelocity);
} }
@ -100,17 +120,21 @@ namespace Passer.Control.Core {
} }
public Spherical angularVelocity; public Spherical angularVelocity;
public virtual void Init(bool invokeEvent = true) { public virtual void Init(bool invokeEvent = true)
{
Thing.Add(this, invokeEvent); Thing.Add(this, invokeEvent);
} }
public Thing(bool initialize = true) { public Thing(bool initialize = true)
if (initialize) { {
if (initialize)
{
//this.Init(); //this.Init();
Thing.Add(this); 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.participant = client;
this.id = thingId; this.id = thingId;
this.type = thingType; this.type = thingType;
@ -119,29 +143,31 @@ namespace Passer.Control.Core {
Thing.Add(this); Thing.Add(this);
} }
public virtual void Update(float currentTime) { public virtual void Update(float currentTime)
{
// should recurse over children... // should recurse over children...
} }
public virtual void ProcessBytes(byte[] bytes) { public virtual void ProcessBytes(byte[] bytes)
{
//if (sensor != null) //if (sensor != null)
// sensor.ProcessBytes(bytes); // sensor.ProcessBytes(bytes);
} }
// Experimental // Experimental
public float stressLevel = 0; // public float stressLevel = 0;
protected delegate void ReceptorFunc(Sensor sensor); // protected delegate void ReceptorFunc(Sensor sensor);
protected void SetupReceptor(Sensor sensor, ReceptorFunc receptor) { // protected void SetupReceptor(Sensor sensor, ReceptorFunc receptor) {
sensor.Signaller += (sensor => Receptor(receptor, sensor)); // sensor.Signaller += (sensor => Receptor(receptor, sensor));
} // }
protected void Receptor(ReceptorFunc receptor, Sensor sensor) { // protected void Receptor(ReceptorFunc receptor, Sensor sensor) {
if (sensor.signalStrength <= stressLevel) // if (sensor.signalStrength <= stressLevel)
return; // return;
receptor(sensor); // receptor(sensor);
} // }
//---------- All Things //---------- All Things
@ -150,40 +176,48 @@ namespace Passer.Control.Core {
public delegate void ThingHandler(Thing t); public delegate void ThingHandler(Thing t);
public static event ThingHandler OnNewThing; 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); Thing foundThing = Get(thing.networkId, thing.id);
if (foundThing == null) { if (foundThing == null)
{
if (thing.id == 0) if (thing.id == 0)
thing.id = (byte)(allThings.Count + 1); thing.id = (byte)(allThings.Count + 1);
allThings.Add(thing); allThings.Add(thing);
if (invokeEvent) if (invokeEvent)
OnNewThing?.Invoke(thing); 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)); Thing thing = allThings.Find(aThing => IsThing(aThing, networkId, thingId));
return thing; 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) if (thing == null)
return false; 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); allThings.RemoveAll(t => t.networkId == networkId && t.id == thingId);
} }
public static Thing[] GetAllThings() { public static Thing[] GetAllThings()
{
return allThings.ToArray(); return allThings.ToArray();
} }
public static void UpdateAll(float currentTime) { public static void UpdateAll(float currentTime)
foreach (Thing thing in allThings) { {
foreach (Thing thing in allThings)
{
if (thing.parent == null) // update only root things if (thing.parent == null) // update only root things
thing.Update(currentTime); thing.Update(currentTime);
} }

272
float16.cs Normal file
View File

@ -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 --
}
}

36
test.sln Normal file
View File

@ -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

17
test/UnitTest1.cs Normal file
View File

@ -0,0 +1,17 @@
namespace test;
using Passer.Control.Core;
public class Tests
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test1()
{
Assert.Pass();
}
}

28
test/test.csproj Normal file
View File

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.3.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ControlCore.csproj" />
</ItemGroup>
</Project>