From 587f10490f2ffb38e84580e1237b37a9132b9fb1 Mon Sep 17 00:00:00 2001 From: Pascal Serrarens Date: Tue, 7 Apr 2026 09:12:24 +0200 Subject: [PATCH] Move out non-subtree NanoBrain --- NanoBrain.meta | 8 - NanoBrain/Cluster.cs | 508 ------- NanoBrain/Cluster.cs.meta | 11 - NanoBrain/ClusterPrefab.cs | 116 -- NanoBrain/ClusterPrefab.cs.meta | 11 - NanoBrain/ClusterReceptor.cs | 214 --- NanoBrain/ClusterReceptor.cs.meta | 11 - NanoBrain/Editor.meta | 8 - NanoBrain/Editor/BrainEditorWindow.cs | 365 ----- NanoBrain/Editor/BrainEditorWindow.cs.meta | 2 - NanoBrain/Editor/BrainPickerWindow.cs | 66 - NanoBrain/Editor/BrainPickerWindow.cs.meta | 11 - NanoBrain/Editor/ClusterInspector.cs | 1100 -------------- NanoBrain/Editor/ClusterInspector.cs.meta | 11 - NanoBrain/Editor/DAGWindow.cs | 393 ----- NanoBrain/Editor/DAGWindow.cs.meta | 11 - NanoBrain/Editor/NanoBrain_Editor.cs | 50 - NanoBrain/Editor/NanoBrain_Editor.cs.meta | 11 - NanoBrain/Editor/Resources.meta | 8 - NanoBrain/Editor/Resources/GraphStyles.uss | 12 - .../Editor/Resources/GraphStyles.uss.meta | 11 - NanoBrain/IReceptor.cs | 73 - NanoBrain/IReceptor.cs.meta | 11 - NanoBrain/Icons.meta | 8 - .../Icons/NeuraalNetwerkIcoonSchets1.png | Bin 60406 -> 0 bytes .../Icons/NeuraalNetwerkIcoonSchets1.png.meta | 117 -- .../Icons/NeuraalNetwerkIcoonSchets2.png | Bin 39373 -> 0 bytes .../Icons/NeuraalNetwerkIcoonSchets2.png.meta | 117 -- .../Icons/NeuraalNetwerkIcoonSchets3.png | Bin 40575 -> 0 bytes .../Icons/NeuraalNetwerkIcoonSchets3.png.meta | 117 -- NanoBrain/Identity.asset | 59 - NanoBrain/Identity.asset.meta | 8 - NanoBrain/LinearAlgebra.meta | 8 - NanoBrain/LinearAlgebra/.editorconfig | 19 - .../.gitea/workflows/unit_tests.yaml | 37 - NanoBrain/LinearAlgebra/.gitignore | 5 - .../LinearAlgebra/LinearAlgebra-csharp.sln | 30 - NanoBrain/LinearAlgebra/src/Angle.cs | 341 ----- NanoBrain/LinearAlgebra/src/Decomposition.cs | 287 ---- NanoBrain/LinearAlgebra/src/Direction.cs | 261 ---- NanoBrain/LinearAlgebra/src/Float.cs | 41 - .../LinearAlgebra/src/LinearAlgebra.csproj | 14 - NanoBrain/LinearAlgebra/src/Matrix.cs | 689 --------- NanoBrain/LinearAlgebra/src/Quat32.cs | 87 -- NanoBrain/LinearAlgebra/src/Quaternion.cs | 582 -------- NanoBrain/LinearAlgebra/src/Spherical.cs | 279 ---- NanoBrain/LinearAlgebra/src/SwingTwist.cs | 136 -- NanoBrain/LinearAlgebra/src/Vector2Float.cs | 479 ------ NanoBrain/LinearAlgebra/src/Vector2Int.cs | 185 --- NanoBrain/LinearAlgebra/src/Vector3Float.cs | 402 ----- NanoBrain/LinearAlgebra/src/Vector3Int.cs | 273 ---- NanoBrain/LinearAlgebra/src/float16.cs | 322 ---- NanoBrain/LinearAlgebra/test/AngleTest.cs | 501 ------- NanoBrain/LinearAlgebra/test/DirectionTest.cs | 226 --- .../test/LinearAlgebra_Test.csproj | 19 - .../LinearAlgebra/test/QuaternionTest.cs | 185 --- NanoBrain/LinearAlgebra/test/SphericalTest.cs | 271 ---- .../LinearAlgebra/test/SwingTwistTest.cs | 131 -- .../LinearAlgebra/test/Vector2FloatTest.cs | 364 ----- .../LinearAlgebra/test/Vector2IntTest.cs | 270 ---- .../LinearAlgebra/test/Vector3FloatTest.cs | 581 -------- .../LinearAlgebra/test/Vector3IntTest.cs | 349 ----- NanoBrain/MemoryCell.cs | 59 - NanoBrain/MemoryCell.cs.meta | 11 - NanoBrain/NanoBrain-Unity.code-workspace | 12 - NanoBrain/NanoBrain-Unity.code-workspace.meta | 7 - NanoBrain/NanoBrain.cs | 31 - NanoBrain/NanoBrain.cs.meta | 11 - NanoBrain/Neuron.cs | 336 ----- NanoBrain/Neuron.cs.meta | 11 - NanoBrain/NewVelocity.asset | 1305 ----------------- NanoBrain/NewVelocity.asset.meta | 8 - NanoBrain/Nucleus.cs | 72 - NanoBrain/Nucleus.cs.meta | 11 - NanoBrain/NucleusArray.cs | 157 -- NanoBrain/NucleusArray.cs.meta | 11 - NanoBrain/Receptor.cs | 78 - NanoBrain/Receptor.cs.meta | 11 - NanoBrain/Scene.meta | 8 - NanoBrain/Scene/TestScene Boid.unity | 487 ------ NanoBrain/Scene/TestScene Boid.unity.meta | 7 - NanoBrain/Scene/TestScene Experiment.unity | 365 ----- .../Scene/TestScene Experiment.unity.meta | 7 - NanoBrain/Scripts.meta | 8 - NanoBrain/Scripts/Experimental.meta | 8 - .../Scripts/NeuraalNetwerkIcoonSchets1.png | Bin 63771 -> 0 bytes .../NeuraalNetwerkIcoonSchets1.png.meta | 117 -- .../Scripts/NeuraalNetwerkIcoonSchets2.png | Bin 39373 -> 0 bytes .../NeuraalNetwerkIcoonSchets2.png.meta | 117 -- NanoBrain/Synapse.cs | 15 - NanoBrain/Synapse.cs.meta | 11 - NanoBrain/Velocity.asset | 128 -- NanoBrain/Velocity.asset.meta | 8 - Samples/Animation/AntAnimator.controller | 4 +- Samples/Animation/AntWalk.anim | 58 +- 95 files changed, 13 insertions(+), 14288 deletions(-) delete mode 100644 NanoBrain.meta delete mode 100644 NanoBrain/Cluster.cs delete mode 100644 NanoBrain/Cluster.cs.meta delete mode 100644 NanoBrain/ClusterPrefab.cs delete mode 100644 NanoBrain/ClusterPrefab.cs.meta delete mode 100644 NanoBrain/ClusterReceptor.cs delete mode 100644 NanoBrain/ClusterReceptor.cs.meta delete mode 100644 NanoBrain/Editor.meta delete mode 100644 NanoBrain/Editor/BrainEditorWindow.cs delete mode 100644 NanoBrain/Editor/BrainEditorWindow.cs.meta delete mode 100644 NanoBrain/Editor/BrainPickerWindow.cs delete mode 100644 NanoBrain/Editor/BrainPickerWindow.cs.meta delete mode 100644 NanoBrain/Editor/ClusterInspector.cs delete mode 100644 NanoBrain/Editor/ClusterInspector.cs.meta delete mode 100644 NanoBrain/Editor/DAGWindow.cs delete mode 100644 NanoBrain/Editor/DAGWindow.cs.meta delete mode 100644 NanoBrain/Editor/NanoBrain_Editor.cs delete mode 100644 NanoBrain/Editor/NanoBrain_Editor.cs.meta delete mode 100644 NanoBrain/Editor/Resources.meta delete mode 100644 NanoBrain/Editor/Resources/GraphStyles.uss delete mode 100644 NanoBrain/Editor/Resources/GraphStyles.uss.meta delete mode 100644 NanoBrain/IReceptor.cs delete mode 100644 NanoBrain/IReceptor.cs.meta delete mode 100644 NanoBrain/Icons.meta delete mode 100644 NanoBrain/Icons/NeuraalNetwerkIcoonSchets1.png delete mode 100644 NanoBrain/Icons/NeuraalNetwerkIcoonSchets1.png.meta delete mode 100644 NanoBrain/Icons/NeuraalNetwerkIcoonSchets2.png delete mode 100644 NanoBrain/Icons/NeuraalNetwerkIcoonSchets2.png.meta delete mode 100644 NanoBrain/Icons/NeuraalNetwerkIcoonSchets3.png delete mode 100644 NanoBrain/Icons/NeuraalNetwerkIcoonSchets3.png.meta delete mode 100644 NanoBrain/Identity.asset delete mode 100644 NanoBrain/Identity.asset.meta delete mode 100644 NanoBrain/LinearAlgebra.meta delete mode 100644 NanoBrain/LinearAlgebra/.editorconfig delete mode 100644 NanoBrain/LinearAlgebra/.gitea/workflows/unit_tests.yaml delete mode 100644 NanoBrain/LinearAlgebra/.gitignore delete mode 100644 NanoBrain/LinearAlgebra/LinearAlgebra-csharp.sln delete mode 100644 NanoBrain/LinearAlgebra/src/Angle.cs delete mode 100644 NanoBrain/LinearAlgebra/src/Decomposition.cs delete mode 100644 NanoBrain/LinearAlgebra/src/Direction.cs delete mode 100644 NanoBrain/LinearAlgebra/src/Float.cs delete mode 100644 NanoBrain/LinearAlgebra/src/LinearAlgebra.csproj delete mode 100644 NanoBrain/LinearAlgebra/src/Matrix.cs delete mode 100644 NanoBrain/LinearAlgebra/src/Quat32.cs delete mode 100644 NanoBrain/LinearAlgebra/src/Quaternion.cs delete mode 100644 NanoBrain/LinearAlgebra/src/Spherical.cs delete mode 100644 NanoBrain/LinearAlgebra/src/SwingTwist.cs delete mode 100644 NanoBrain/LinearAlgebra/src/Vector2Float.cs delete mode 100644 NanoBrain/LinearAlgebra/src/Vector2Int.cs delete mode 100644 NanoBrain/LinearAlgebra/src/Vector3Float.cs delete mode 100644 NanoBrain/LinearAlgebra/src/Vector3Int.cs delete mode 100644 NanoBrain/LinearAlgebra/src/float16.cs delete mode 100644 NanoBrain/LinearAlgebra/test/AngleTest.cs delete mode 100644 NanoBrain/LinearAlgebra/test/DirectionTest.cs delete mode 100644 NanoBrain/LinearAlgebra/test/LinearAlgebra_Test.csproj delete mode 100644 NanoBrain/LinearAlgebra/test/QuaternionTest.cs delete mode 100644 NanoBrain/LinearAlgebra/test/SphericalTest.cs delete mode 100644 NanoBrain/LinearAlgebra/test/SwingTwistTest.cs delete mode 100644 NanoBrain/LinearAlgebra/test/Vector2FloatTest.cs delete mode 100644 NanoBrain/LinearAlgebra/test/Vector2IntTest.cs delete mode 100644 NanoBrain/LinearAlgebra/test/Vector3FloatTest.cs delete mode 100644 NanoBrain/LinearAlgebra/test/Vector3IntTest.cs delete mode 100644 NanoBrain/MemoryCell.cs delete mode 100644 NanoBrain/MemoryCell.cs.meta delete mode 100644 NanoBrain/NanoBrain-Unity.code-workspace delete mode 100644 NanoBrain/NanoBrain-Unity.code-workspace.meta delete mode 100644 NanoBrain/NanoBrain.cs delete mode 100644 NanoBrain/NanoBrain.cs.meta delete mode 100644 NanoBrain/Neuron.cs delete mode 100644 NanoBrain/Neuron.cs.meta delete mode 100644 NanoBrain/NewVelocity.asset delete mode 100644 NanoBrain/NewVelocity.asset.meta delete mode 100644 NanoBrain/Nucleus.cs delete mode 100644 NanoBrain/Nucleus.cs.meta delete mode 100644 NanoBrain/NucleusArray.cs delete mode 100644 NanoBrain/NucleusArray.cs.meta delete mode 100644 NanoBrain/Receptor.cs delete mode 100644 NanoBrain/Receptor.cs.meta delete mode 100644 NanoBrain/Scene.meta delete mode 100644 NanoBrain/Scene/TestScene Boid.unity delete mode 100644 NanoBrain/Scene/TestScene Boid.unity.meta delete mode 100644 NanoBrain/Scene/TestScene Experiment.unity delete mode 100644 NanoBrain/Scene/TestScene Experiment.unity.meta delete mode 100644 NanoBrain/Scripts.meta delete mode 100644 NanoBrain/Scripts/Experimental.meta delete mode 100644 NanoBrain/Scripts/NeuraalNetwerkIcoonSchets1.png delete mode 100644 NanoBrain/Scripts/NeuraalNetwerkIcoonSchets1.png.meta delete mode 100644 NanoBrain/Scripts/NeuraalNetwerkIcoonSchets2.png delete mode 100644 NanoBrain/Scripts/NeuraalNetwerkIcoonSchets2.png.meta delete mode 100644 NanoBrain/Synapse.cs delete mode 100644 NanoBrain/Synapse.cs.meta delete mode 100644 NanoBrain/Velocity.asset delete mode 100644 NanoBrain/Velocity.asset.meta diff --git a/NanoBrain.meta b/NanoBrain.meta deleted file mode 100644 index 2c2f132..0000000 --- a/NanoBrain.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: c5f07463dfa08efc1bad0f3039a4cecc -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Cluster.cs b/NanoBrain/Cluster.cs deleted file mode 100644 index 996fb2c..0000000 --- a/NanoBrain/Cluster.cs +++ /dev/null @@ -1,508 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; -using Unity.Mathematics; -using static Unity.Mathematics.math; - -[Serializable] -public class Cluster : Nucleus { - - public string baseName { - get { - int colonPositon = this.name.IndexOf(':'); - if (colonPositon < 0) - return this.name; - return this.name[..colonPositon]; - } - } - - #region Init - - public Cluster(ClusterPrefab prefab, Cluster parent) { - this.prefab = prefab; - this.name = prefab.name; - - this.parent = parent; - this.parent?.clusterNuclei.Add(this); - - ClonePrefab(); - _ = this.inputs; - this.sortedNuclei = TopologicalSort(this.clusterNuclei); - } - - public Cluster(ClusterPrefab prefab, ClusterPrefab parent = null) { - this.prefab = prefab; - this.name = prefab.name; - this.clusterPrefab = parent; - - if (this.clusterPrefab != null) - this.clusterPrefab.nuclei.Add(this); - - ClonePrefab(); - _ = this.inputs; - this.sortedNuclei = TopologicalSort(this.clusterNuclei); - } - - private void ClonePrefab() { - Nucleus[] prefabNuclei = this.prefab.nuclei.ToArray(); - // first clone the nuclei without their connections - foreach (Nucleus nucleus in this.prefab.nuclei) { - nucleus.ShallowCloneTo(this); - } - Nucleus[] clonedNuclei = this.clusterNuclei.ToArray(); - - // Now clone the connections - for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) { - Nucleus prefabNucleus = prefabNuclei[nucleusIx]; - if (prefabNucleus is not Neuron prefabNeuron) - continue; - - Nucleus clonedNucleus = clonedNuclei[nucleusIx]; - if (clonedNucleus == null || clonedNucleus is not Neuron clonedNeuron) - continue; - - // Copy the receivers, which will also create the synapses - // Clusters do not have receivers... - foreach (Nucleus receiver in prefabNeuron.receivers.ToArray()) { - int ix = GetNucleusIndex(prefabNuclei, receiver); - if (ix < 0) - continue; - - if (clonedNuclei[ix] is not Nucleus clonedReceiver) - continue; - - // Find the synapse for the weight - float weight = 1; - foreach (Synapse synapse in receiver.synapses) { - // Find the weight for this synapse - if (synapse.neuron == prefabNucleus) { - weight = synapse.weight; - break; - } - } - - clonedNeuron.AddReceiver(clonedReceiver, weight); - } - } - - // Copy nucleus arrays for receptors - for (int nucleusIx = 0; nucleusIx < prefabNuclei.Length; nucleusIx++) { - Nucleus prefabNucleus = prefabNuclei[nucleusIx]; - if (prefabNucleus is not IReceptor prefabReceptor) - continue; - - if (prefabReceptor.nucleiArray == null || prefabReceptor.nucleiArray.Length == 0) - continue; - - IReceptor clonedNucleus = clonedNuclei[nucleusIx] as IReceptor; - if (prefabReceptor == prefabReceptor.nucleiArray[0]) { - // We clone the array only for the first entry - NucleusArray clonedArray = new(prefabReceptor.nucleiArray.Length, "array"); - int arrayIx = 0; - foreach (Nucleus prefabArrayNucleus in prefabReceptor.nucleiArray) { - int arrayNucleusIx = GetNucleusIndex(prefabNuclei, prefabArrayNucleus); - if (arrayNucleusIx >= 0) { - Nucleus clonedArrayNucleus = clonedNuclei[arrayNucleusIx]; - clonedArray.nuclei[arrayIx] = clonedArrayNucleus; - } - else { - Debug.LogError($" Could not find prefab nucleus {prefabNucleus.name} in the clones"); - } - arrayIx++; - } - //clonedNucleus.array = clonedArray; - clonedNucleus.nucleiArray = clonedArray.nuclei; - } - else { - // The others will refer to the array created for the first nucleus in the array - int firstNucleusIx = GetNucleusIndex(prefabNuclei, prefabReceptor.nucleiArray[0]); - IReceptor clonedFirstNucleus = clonedNuclei[firstNucleusIx] as IReceptor; - clonedNucleus.nucleiArray = clonedFirstNucleus.nucleiArray; - } - } - - foreach (Nucleus nucleus in this.clusterNuclei) { - if (nucleus is Cluster clonedSubCluster) - RestoreAllExternalReceivers(clonedSubCluster, this.prefab, this); - } - } - - // Sort the nuclei in a correct evaluation order - private List TopologicalSort(List nodes) { - Dictionary inDegree = new(); - foreach (Nucleus node in nodes) - inDegree[node] = 0; // Initialize in-degree to zero - - // Calculate in-degrees - foreach (Nucleus node in nodes) { - if (node is Cluster cluster) { - foreach (Nucleus receiver in cluster.CollectReceivers()) - inDegree[receiver]++; - } - else if (node is Neuron neuron) { - foreach (Nucleus receiver in neuron.receivers) - inDegree[receiver]++; - } - } - - Queue queue = new(); - foreach (Nucleus node in nodes) { - if (inDegree[node] == 0) // Nodes with no dependencies - queue.Enqueue(node); - } - // The queue basically stores all input nuclei? - - List sortedOrder = new(); - while (queue.Count > 0) { - Nucleus current = queue.Dequeue(); - sortedOrder.Add(current); // Process the node - - if (current is Neuron neuron) { - foreach (Nucleus receiver in neuron.receivers) { - inDegree[receiver]--; - if (inDegree[receiver] == 0) // If all dependencies resolved - queue.Enqueue(receiver); - } - } - else if (current is Cluster cluster) { - foreach (Nucleus receiver in cluster.CollectReceivers()) { - inDegree[receiver]--; - if (inDegree[receiver] == 0) // If all dependencies resolved - queue.Enqueue(receiver); - } - } - } - - // Check for cycles in the graph - if (sortedOrder.Count != nodes.Count) - throw new InvalidOperationException("Graph is not a DAG; a cycle exists."); - - return sortedOrder; - } - - public override Nucleus Clone(ClusterPrefab parent) { - Cluster clone = new(this.prefab, parent); - - foreach (Synapse synapse in this.synapses) { - Synapse clonedSynapse = clone.AddSynapse(synapse.neuron); - clonedSynapse.weight = synapse.weight; - } - - foreach (Neuron output in this.outputs) { - foreach (Nucleus receiver in output.receivers) { - int ix = GetNucleusIndex(this.clusterNuclei.ToArray(), output); - if (ix < 0) - continue; - - if (clone.clusterNuclei[ix] is not Neuron clonedOutput) - continue; - - clonedOutput.AddReceiver(receiver); - } - } - - return clone; - } - - public override Nucleus ShallowCloneTo(Cluster parent) { - Cluster clone = new(this.prefab, parent) { - name = this.name, - clusterPrefab = this.clusterPrefab, - }; - - return clone; - } - - private static void RestoreAllExternalReceivers(Cluster clonedCluster, ClusterPrefab prefabParent, Cluster clonedParent) { - int clonedClusterIx = GetNucleusIndex(clonedParent.clusterNuclei, clonedCluster); - if (prefabParent.nuclei[clonedClusterIx] is not Cluster sourceCluster) - return; - - for (int nucleusIx = 0; nucleusIx < sourceCluster.clusterNuclei.Count; nucleusIx++) { - Nucleus sourceNucleus = sourceCluster.clusterNuclei[nucleusIx]; - if (sourceNucleus is not Neuron sourceNeuron) - continue; - - if (clonedCluster.clusterNuclei[nucleusIx] is not Neuron clonedNeuron) - continue; - - // copy the receivers (and thus synapses) from the source to the clone - foreach (Nucleus receiver in sourceNeuron.receivers) { - int ix = GetNucleusIndex(prefabParent.nuclei, receiver); - if (ix < 0 || ix >= clonedParent.clusterNuclei.Count) - continue; - - Nucleus clonedReceiver = clonedParent.clusterNuclei[ix]; - - // Find the synapse for the weight - float weight = 1; - foreach (Synapse synapse in receiver.synapses) { - // Find the weight for this synapse - if (synapse.neuron == sourceNucleus) { - weight = synapse.weight; - break; - } - } - - clonedNeuron.AddReceiver(clonedReceiver, weight); - // Debug.Log($"external: {clonedReceiver.name} receives from {clonedNeuron.name} {clonedNeuron.GetHashCode()}"); - } - } - } - - protected int GetNucleusIndex(Nucleus[] nuclei, Nucleus nucleus) { - for (int i = 0; i < nuclei.Length; i++) { - if (nucleus == nuclei[i]) - return i; - } - return -1; - } - - public static int GetNucleusIndex(List nuclei, Nucleus nucleus) { - int i = 0; - foreach (Nucleus nucleiElement in nuclei) { - //for (int i = 0; i < nuclei.Length; i++) { - if (nucleus == nucleiElement) - return i; - i++; - } - return -1; - } - - #endregion Init - - public ClusterPrefab prefab; - - - [SerializeReference] - public List clusterNuclei = new(); - // the nuclei sorted using topological sorting - // to ensure that the cluster is computer in the right order - public List sortedNuclei; - //public Dictionary nucleiDict = new(); - - public List _inputs = null; - public virtual List inputs { - get { - if (this._inputs == null) { - this._inputs = new(); - foreach (Nucleus nucleus in this.clusterNuclei) { - // inputs have no synapses - if (nucleus.synapses.Count == 0) - this._inputs.Add(nucleus); - } - ComputeOrders(); - } - return this._inputs; - } - } - - public Dictionary> computeOrders = new(); - private void ComputeOrders() { - foreach (Nucleus input in this._inputs) - computeOrders[input] = TopologicalSort2(input); - } - - private List TopologicalSort2(Nucleus startNode) { - Dictionary inDegree = new(); - HashSet visited = new(); - - // Initialize in-degrees and mark all nodes as unvisited - foreach (Nucleus node in this.clusterNuclei) - inDegree[node] = 0; - - // Calculate in-degrees for all nodes reachable from the start node - Queue queue = new Queue(); - queue.Enqueue(startNode); - visited.Add(startNode); - - while (queue.Count > 0) { - Nucleus current = queue.Dequeue(); - List receivers = null; - if (current is Neuron neuron) - receivers = neuron.receivers; - else if (current is Cluster cluster) - receivers = cluster.CollectReceivers(); - - // if (current is Neuron neuron) { - foreach (Nucleus receiver in receivers) { - if (!visited.Contains(receiver)) { - visited.Add(receiver); - queue.Enqueue(receiver); - } - inDegree[receiver]++; - } - // } - } - - // Perform topological sort on all reachable nodes - queue.Clear(); - foreach (Nucleus node in visited) { - if (inDegree[node] == 0) - queue.Enqueue(node); - } - - List sortedOrder = new List(); - while (queue.Count > 0) { - Nucleus current = queue.Dequeue(); - sortedOrder.Add(current); // Process the node - - List receivers = null; - if (current is Neuron neuron) - receivers = neuron.receivers; - else if (current is Cluster cluster) - receivers = cluster.CollectReceivers(); - - //if (current is Neuron neuron) { - - foreach (Nucleus receiver in receivers) { - if (visited.Contains(receiver)) { - inDegree[receiver]--; - if (inDegree[receiver] == 0) // If all dependencies resolved - queue.Enqueue(receiver); - } - } - //} - } - - // Check for cycles in the graph - if (sortedOrder.Count != visited.Count) - throw new InvalidOperationException("Graph is not a DAG; a cycle exists."); - - return sortedOrder; - } - - public virtual Neuron defaultOutput {//=> this.nuclei[0] as Nucleus; - get { - if (this.clusterNuclei.Count > 0) - return this.clusterNuclei[0] as Neuron; - return null; - } - } - protected List _outputs = null; - public List outputs { - get { - if (this._outputs == null) { - this._outputs = new(); - foreach (Nucleus nucleus in this.clusterNuclei) { - if (nucleus is Neuron neuron) // && neuron.receivers.Count == 0) - this._outputs.Add(neuron); - } - } - return this._outputs; - } - } - - public bool TryGetNucleus(string nucleusName, out Nucleus foundNucleus) { - foreach (Nucleus receptor in this.clusterNuclei) { - if (receptor is Nucleus nucleus) - if (nucleus.name == nucleusName) { - foundNucleus = nucleus; - return true; - } - } - foundNucleus = null; - return false; - } - - public Nucleus GetNucleus(string nucleusName) { - int dotPosition = nucleusName.IndexOf('.'); - if (dotPosition >= 0) { - string clusterName = nucleusName[..dotPosition]; - string clusterName0 = clusterName + ": 0"; - foreach (Nucleus nucleus in this.clusterNuclei) { - if (nucleus is Cluster cluster) { - if (cluster.name == clusterName || cluster.name == clusterName0) { - string subNucleusName = nucleusName[(dotPosition + 1)..]; - return cluster.GetNucleus(subNucleusName); - } - } - } - return null; - } - else { - string nucleusName0 = nucleusName + ": 0"; - foreach (Nucleus nucleus in this.clusterNuclei) { - if (nucleus is IReceptor receptor) { - if (nucleus.name == nucleusName | nucleus.name == nucleusName0) - return nucleus; - } - else if (nucleus.name == nucleusName) - return nucleus; - } - return null; - } - } - - // [Obsolete("Use GetNucleus instead")] - // public IReceptor GetReceptor(string receptorName) { - // return GetNucleus(receptorName) as IReceptor; - // } - - #region Receivers - - public virtual List CollectReceivers() { - List receivers = new(); - foreach (Neuron output in this.outputs) { - foreach (Nucleus receiver in output.receivers) { - // Only add receivers outside this cluster - if (receiver.clusterPrefab != this.prefab) - receivers.Add(receiver); - //receivers.AddRange(output.receivers); - } - } - return receivers; - } - - #endregion Receivers - - #region Update - - public void UpdateFromNucleus(Nucleus startNucleus) { - // no bias+synapse input state calculation for now... - - if (this.computeOrders.ContainsKey(startNucleus) == false) { - //Debug.LogError($"{this.name} compute orders does not contain an order for {startNucleus.name}"); - return; - } - - List computeOrder = this.computeOrders[startNucleus]; - if (startNucleus.trace) - Debug.Log($"Update from {startNucleus.name}"); - foreach (Nucleus nucleus in computeOrder) { - nucleus.UpdateStateIsolated(); - if (startNucleus.trace && nucleus is Neuron neuron) - Debug.Log($" {nucleus.name}[{nucleus.GetHashCode()}] = {neuron.outputValue}"); - } - - // continue in parent - this.parent?.UpdateFromNucleus(this); - - UpdateNuclei(); - } - - public override void UpdateStateIsolated() { - throw new Exception("Cluster should not be updated!"); - // float3 sum = this.bias; - - // //Applying the weight factors - // foreach (Synapse synapse in this.synapses) { - // if (lengthsq(synapse.neuron.outputValue) > 0) { - // sum += synapse.weight * synapse.neuron.outputValue; - // } - // } - - // foreach (Nucleus nucleus in this.sortedNuclei) - // nucleus.UpdateStateIsolated(); - - // UpdateNuclei(); - } - - public override void UpdateNuclei() { - foreach (Nucleus nucleus in this.clusterNuclei) - nucleus.UpdateNuclei(); - } - - #endregion Update - -} diff --git a/NanoBrain/Cluster.cs.meta b/NanoBrain/Cluster.cs.meta deleted file mode 100644 index c77fea0..0000000 --- a/NanoBrain/Cluster.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f13cdc4a175a9f379a00317ae68d8bea -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/ClusterPrefab.cs b/NanoBrain/ClusterPrefab.cs deleted file mode 100644 index 760e8bb..0000000 --- a/NanoBrain/ClusterPrefab.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -[CreateAssetMenu(menuName = "Passer/Cluster")] -public class ClusterPrefab : ScriptableObject { - // The ScriptableObject asset from which the runtime object has been created - - [SerializeReference] - public List nuclei = new(); - - - public virtual Nucleus output => this.nuclei[0] as Nucleus; - - public List _inputs = null; - public virtual List inputs { - get { - if (this._inputs == null) { - this._inputs = new(); - foreach (Nucleus receptor in this.nuclei) { - if (receptor is Nucleus nucleus) { - // inputs have no incoming synapses yet. - if (nucleus.synapses.Count == 0) - this._inputs.Add(nucleus); - } - } - } - return this._inputs; - } - } - private List _outputs = null; - public List outputs { - get { - if (this._outputs == null) - RefreshOutputs(); - return this._outputs; - } - } - public void RefreshOutputs() { - this._outputs = new(); - foreach (Nucleus nucleus in this.nuclei) { - if (nucleus is Neuron neuron && neuron.receivers.Count == 0) - this._outputs.Add(nucleus); - } - } - - public Nucleus GetNucleus(string nucleusName) { - foreach (Nucleus nucleus in this.nuclei) { - if (nucleus.name == nucleusName) - return nucleus; - } - return null; - } - - // Call this function to ensure that there is at least one nucleus - // This is an invariant and should be ensured before the nucleus is used - // because output requires it. - public void EnsureInitialization() { - nuclei ??= new List(); - if (nuclei.Count == 0) - new Neuron(this, "Output"); // Every cluster should have at least 1 neuron - } - - public void GarbageCollection() { - HashSet visitedNuclei = new(); - foreach (Nucleus output in this.outputs) - MarkNuclei(visitedNuclei, output); - //Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei"); - this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false); - } - - public void MarkNuclei(HashSet visitedNuclei, Nucleus nucleus) { - if (nucleus is null) - return; - - if (nucleus.parent != null && nucleus.parent.prefab != this) - visitedNuclei.Add(nucleus.parent); - else - visitedNuclei.Add(nucleus); - if (nucleus.synapses != null) { - HashSet visitedSynapses = new(); - foreach (Synapse synapse in nucleus.synapses) { - if (synapse != null && synapse.neuron != null) { - visitedSynapses.Add(synapse); - if (synapse.neuron is Nucleus synapse_nucleus) - MarkNuclei(visitedNuclei, synapse_nucleus); - } - } - nucleus.synapses.RemoveAll(synapse => visitedSynapses.Contains(synapse) == false); - } - if (nucleus is Neuron neuron && neuron.receivers != null) { - HashSet visitedReceivers = new(); - foreach (Nucleus receiver in neuron.receivers) { - if (receiver != null && receiver != null) { - visitedReceivers.Add(receiver); - visitedNuclei.Add(receiver); - } - } - neuron.receivers.RemoveAll(receiver => visitedReceivers.Contains(receiver) == false); - } - } - - public virtual void UpdateNuclei() { - foreach (Nucleus nucleus in this.nuclei) - nucleus.UpdateNuclei(); - } - - public int GetNucleusIndex(Nucleus receiver) { - int ix = 0; - foreach (Nucleus nucleus in this.nuclei) { - if (receiver == nucleus) - return ix; - ix++; - } - return -1; - } -} diff --git a/NanoBrain/ClusterPrefab.cs.meta b/NanoBrain/ClusterPrefab.cs.meta deleted file mode 100644 index d8dad7a..0000000 --- a/NanoBrain/ClusterPrefab.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 60a957541c24c57e78018c202ebb1d9b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/ClusterReceptor.cs b/NanoBrain/ClusterReceptor.cs deleted file mode 100644 index ac65e7a..0000000 --- a/NanoBrain/ClusterReceptor.cs +++ /dev/null @@ -1,214 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; -using Unity.Mathematics; -using static Unity.Mathematics.math; -using System.Linq; - -[Serializable] -public class ClusterReceptor : Cluster, IReceptor { - public ClusterReceptor(ClusterPrefab prefab, Cluster parent, string name) : base(prefab, parent) { - this.name = name; - this.array = new NucleusArray(this); - if (this.name.IndexOf(":") < 0) - this.name += ": 0"; - - } - public ClusterReceptor(ClusterPrefab prefab, ClusterPrefab parent, string name) : base(prefab, parent) { - this.name = name; - this.array = new NucleusArray(this); - } - - public string GetName() { - return this.name; - } - - public override Nucleus ShallowCloneTo(Cluster parent) { - ClusterReceptor clone = new(this.prefab, parent, this.name) { - clusterPrefab = this.clusterPrefab, - }; - - return clone; - } - - public override Nucleus Clone(ClusterPrefab parent) { - ClusterReceptor clone = new(prefab, parent, this.name) { - array = this._array - }; - - foreach (Synapse synapse in this.synapses) { - Synapse clonedSynapse = clone.AddSynapse(synapse.neuron); - clonedSynapse.weight = synapse.weight; - } - - this._outputs = null; // Make sure the output are regenerated - foreach (Neuron output in this.outputs) { - int ix = GetNucleusIndex(this.clusterNuclei, output); - if (ix < 0 || clone.clusterNuclei[ix] is not Neuron clonedOutput) - continue; - - foreach (Nucleus receiver in output.receivers) - clonedOutput.AddReceiver(receiver); - } - return clone; - } - - public override List CollectReceivers() { - List receivers = new(); - foreach (Nucleus element in this.nucleiArray) { - if (element is not Cluster clusterElement) - continue; - - foreach (Nucleus outputNucleus in clusterElement.clusterNuclei) { - if (outputNucleus is not Neuron output) - continue; - - // this should be clusterElement.outputs, - // but outputs is not updated when correctly and may contain old data... - foreach (Nucleus receiver in output.receivers) { - // Only add receivers outside clusterElement cluster - if (receiver.clusterPrefab != clusterElement.prefab && - receivers.Contains(receiver) == false) - receivers.Add(receiver); - } - } - } - return receivers; - } - - [SerializeReference] - private NucleusArray _array; - public NucleusArray array { - set { _array = value; } - } - - public Nucleus[] nucleiArray { - get { return _array.nuclei; } - set { _array.nuclei = value; } - } - - public void AddReceptorElement(ClusterPrefab prefab) { - IReceptorHelpers.AddReceptorElement(this, prefab); - } - - public void RemoveReceptorElement() { - IReceptorHelpers.RemoveReceptorElement(this); - } - - public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) { - IReceptorHelpers.AddArrayReceiver(this, receiverToAdd, weight); - } - - public override void UpdateStateIsolated() { - // Clusters don't do anything, - // The nuclei in them do the work - // and should be called directly, not from the cluster - } - - public override void UpdateNuclei() { - foreach (Nucleus nucleus in this.clusterNuclei) - nucleus.UpdateNuclei(); - } - - public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) { - Debug.LogError("Process Stimulus was called on clusterreceptor without a neuron specified"); - } - - private readonly Dictionary thingReceivers = new(); - - public virtual void ProcessStimulus(Neuron input, Vector3 inputValue, int thingId = 0, string thingName = null) { - CleanupReceivers(); - - if (!thingReceivers.TryGetValue(thingId, out ClusterReceptor selectedReceiver)) - selectedReceiver = FindReceiver2(thingId, inputValue, input); - if (selectedReceiver == null) - return; - - if (thingName != null) { - string baseName = selectedReceiver.name; - int colonPos = selectedReceiver.name.IndexOf(":"); - if (colonPos > 0) - baseName = selectedReceiver.name[..colonPos]; - selectedReceiver.name = baseName + ": " + thingName; - } - - int inputIx = GetNucleusIndex(this.clusterNuclei, input); - if (inputIx < 0) - return; - - if (selectedReceiver.clusterNuclei[inputIx] is Neuron selectedNeuron) - selectedNeuron.ProcessStimulusDirect(inputValue); - } - - private ClusterReceptor FindReceiver2(int thingId, float3 inputValue, Neuron input) { - // No existing nucleus for this thing - ClusterReceptor selectedReceiver = null; - float selectedMagnitude = 0; - foreach (ClusterReceptor receiver in this.nucleiArray.Cast()) { - if (thingReceivers.ContainsValue(receiver) == false) { - // We found an unusued receiver - thingReceivers.Add(thingId, receiver); - return receiver; - } - else if (receiver.defaultOutput.isSleeping) { - // A sleeping receiver is not active and can therefore always be used - thingReceivers.Add(thingId, receiver); - receiver.bias = float3(0, 0, 0); - return receiver; - } - else if (selectedReceiver == null) { - // If we haven't found a receiver yet, just start by taking the first - selectedReceiver = receiver; - selectedMagnitude = length(selectedReceiver.defaultOutput.outputValue); - } - // Look for the receiver with the lowest output magnitude - else { - float magnitude = length(receiver.defaultOutput.outputValue); - - if (length(receiver.defaultOutput.outputValue) < selectedMagnitude) { - selectedReceiver = receiver; - selectedMagnitude = length(selectedReceiver.defaultOutput.outputValue); - } - } - } - if (selectedReceiver != null) { - // To re-initialize the cluster (esp. memory cells) - // we update the cluster neuron twice. - // Bit of a hack..... - int inputIx = GetNucleusIndex(this.clusterNuclei, input); - if (inputIx >= 0) { - if (selectedReceiver.clusterNuclei[inputIx] is Neuron selectedNeuron) - selectedNeuron.ProcessStimulusDirect(inputValue); - } - - // Replace the receiver - // Find the thingId current associated with the receiver - int keyToRemove = thingReceivers.FirstOrDefault(r => r.Value.Equals(selectedReceiver)).Key; - if (keyToRemove != 0 || thingReceivers.ContainsKey(keyToRemove)) - thingReceivers.Remove(keyToRemove); - // And add the new association - thingReceivers.Add(thingId, selectedReceiver); - } - return selectedReceiver; - } - - - private void CleanupReceivers() { - // Remove a thing-receiver connection when the nucleus is inactive - List receiversToRemove = new(); - foreach (KeyValuePair item in thingReceivers) { - if (item.Value != null && item.Value.defaultOutput.isSleeping) - receiversToRemove.Add(item.Key); - } - foreach (int thingId in receiversToRemove) { - Nucleus selectedReceiver = thingReceivers[thingId]; - - thingReceivers.Remove(thingId); - - int colonPos = selectedReceiver.name.IndexOf(":"); - if (colonPos > 0) - selectedReceiver.name = selectedReceiver.name[..colonPos]; - - } - } -} \ No newline at end of file diff --git a/NanoBrain/ClusterReceptor.cs.meta b/NanoBrain/ClusterReceptor.cs.meta deleted file mode 100644 index e3543da..0000000 --- a/NanoBrain/ClusterReceptor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4f64f5d72a422a7c8bb9ace598432aad -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Editor.meta b/NanoBrain/Editor.meta deleted file mode 100644 index 090b3ac..0000000 --- a/NanoBrain/Editor.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 3aedf57a50b6dfa46a59457c87b8ef9d -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Editor/BrainEditorWindow.cs b/NanoBrain/Editor/BrainEditorWindow.cs deleted file mode 100644 index 11bba19..0000000 --- a/NanoBrain/Editor/BrainEditorWindow.cs +++ /dev/null @@ -1,365 +0,0 @@ -using UnityEngine; -using UnityEditor; -using System.Collections.Generic; -using System.Linq; - -// Simple DAG data model -[System.Serializable] -public class DagNode { - public int id; - public string title; - public Vector2 position; - public float radius = 20f; // circle radius -} - -[System.Serializable] -public class DagEdge { - public int fromId; - public int toId; -} - -public class BrainEditorWindow : EditorWindow { - readonly List nodes = new(); - readonly List edges = new(); - - Vector2 pan = Vector2.zero; - float zoom = 1.0f; - const float minZoom = 0.5f; - const float maxZoom = 2.0f; - - // Vector2 dragStart; - // bool draggingNode = false; - // int draggingNodeId = -1; - - private readonly System.Type acceptedType = typeof(ClusterPrefab); - - [MenuItem("Window/Brain Viewer")] - public static void ShowWindow() { - var w = GetWindow("Brain Viewer"); - w.minSize = new Vector2(500, 300); - } - - void OnEnable() { - // if (nodes.Count == 0) - // CreateSampleGraph(); - - - // Register callback so window updates when selection changes - Selection.selectionChanged += OnSelectionChanged; - RefreshSelection(); - ComputeLeftToRightLayout(); - } - - private void OnDisable() { - Selection.selectionChanged -= OnSelectionChanged; - } - - private void OnSelectionChanged() { - RefreshSelection(); - ComputeLeftToRightLayout(); - Repaint(); - } - - private void RefreshSelection() { - ClusterPrefab prefab = Selection.activeObject as ClusterPrefab; - if (prefab != null && acceptedType.IsAssignableFrom(prefab.GetType())) { - GenerateGraph(prefab); - } - } - - private void GenerateGraph(ClusterPrefab prefab) { - nodes.Clear(); - edges.Clear(); - - int ix = 0; - foreach (Nucleus nucleus in prefab.nuclei) { - nodes.Add(new DagNode() { id = ix, title = nucleus.name }); - if (nucleus is Neuron neuron) { - foreach (Nucleus receiver in neuron.receivers) { - int receiverIx = prefab.GetNucleusIndex(receiver); - edges.Add(new DagEdge() { fromId = ix, toId = receiverIx }); - } - } - ix++; - } - } - - - // void CreateSampleGraph() { - // nodes.Clear(); - // edges.Clear(); - - // nodes.Add(new DagNode() { id = 0, title = "In1" }); - // nodes.Add(new DagNode() { id = 1, title = "In2" }); - // nodes.Add(new DagNode() { id = 2, title = "A" }); - // nodes.Add(new DagNode() { id = 3, title = "B" }); - // nodes.Add(new DagNode() { id = 4, title = "C" }); - // nodes.Add(new DagNode() { id = 5, title = "Out1" }); - // nodes.Add(new DagNode() { id = 6, title = "Out2" }); - - // edges.Add(new DagEdge() { fromId = 0, toId = 2 }); - // edges.Add(new DagEdge() { fromId = 1, toId = 2 }); - // edges.Add(new DagEdge() { fromId = 2, toId = 3 }); - // edges.Add(new DagEdge() { fromId = 2, toId = 4 }); - // edges.Add(new DagEdge() { fromId = 3, toId = 5 }); - // edges.Add(new DagEdge() { fromId = 4, toId = 6 }); - // } - - void OnGUI() { - HandleInput(); - - Rect rect = new(0, 0, position.width, position.height); - EditorGUI.DrawRect(rect, new Color(0.11f, 0.11f, 0.11f)); - - // compute window center - Vector2 windowCenter = new(position.width / 2f, position.height / 2f); - - // compute graph bounds center (in graph space) - Rect bounds = GetGraphBounds(); - Vector2 graphCenter = bounds.center; - - // compute autoPan that recenters the graph (does not modify node positions) - Vector2 autoPan = -graphCenter; // moves graph center to origin - // total translation = windowCenter + autoPan + user pan - Matrix4x4 oldMatrix = GUI.matrix; - GUI.matrix = Matrix4x4.TRS(windowCenter + autoPan + pan, Quaternion.identity, Vector3.one * zoom) * - Matrix4x4.TRS(-windowCenter, Quaternion.identity, Vector3.one); - - - // Draw edges first - foreach (DagEdge e in edges) { - DagNode from = GetNodeById(e.fromId); - DagNode to = GetNodeById(e.toId); - if (from == null || to == null) continue; - DrawEdgeCircleNodes(from, to); - } - - // Draw nodes (circles) - foreach (DagNode n in nodes) - DrawNucleus(n); - - GUI.matrix = oldMatrix; - - // Footer toolbar - GUILayout.FlexibleSpace(); - EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); - if (GUILayout.Button("Fit", EditorStyles.toolbarButton)) FitToView(); - if (GUILayout.Button("Layout LR", EditorStyles.toolbarButton)) ComputeLeftToRightLayout(); - EditorGUILayout.EndHorizontal(); - } - - void HandleInput() { - Event e = Event.current; - - // Zoom with scroll - if (e.type == EventType.ScrollWheel) { - float oldZoom = zoom; - float delta = -e.delta.y * 0.01f; - zoom = Mathf.Clamp(zoom + delta, minZoom, maxZoom); - Vector2 mouse = e.mousePosition; - pan += (mouse - new Vector2(position.width / 2, position.height / 2)) * (1 - zoom / oldZoom); - e.Use(); - } - - // Pan with middle or right+ctrl drag - if (e.type == EventType.MouseDrag && (e.button == 2 || (e.button == 1 && e.control))) { - pan += e.delta; - e.Use(); - } - } - - DagNode GetNodeById(int id) => nodes.FirstOrDefault(x => x.id == id); - List GetIncomingEdges(DagNode node) { - List incoming = new(); - foreach (DagEdge e in edges) { - if (e.toId == node.id) - incoming.Add(e); - } - return incoming; - } - List GetOutgoingEdges(DagNode node) { - List outgoing = new(); - foreach (DagEdge e in edges) { - if (e.fromId == node.id) - outgoing.Add(e); - } - return outgoing; - } - - void DrawNucleus(DagNode n) { - Vector3 position = n.position; - - Handles.color = Color.white * 0.9f; - Handles.DrawSolidDisc(n.position, Vector3.forward, n.radius); - - if (GetIncomingEdges(n).Count == 0) - DrawArrowHead(n.position - new Vector2(n.radius + 10, 0), n.position - new Vector2(n.radius + 5, 0), 10f / zoom, 12f / zoom, Color.white); - if (GetOutgoingEdges(n).Count == 0) - DrawArrowHead(n.position + new Vector2(n.radius + 10, 0), n.position + new Vector2(n.radius + 15, 0), 10f / zoom, 12f / zoom, Color.white); - - Handles.color = Color.white; - GUIStyle style = new(EditorStyles.label) { - alignment = TextAnchor.UpperCenter, - normal = { textColor = Color.white }, - fontStyle = FontStyle.Bold, - }; - Vector3 labelPos = position - Vector3.down * (n.radius + 10f); // below disc along up axis - Handles.Label(labelPos, n.title, style); - } - - void DrawEdgeCircleNodes(DagNode from, DagNode to) { - Vector2 a = from.position; - Vector2 b = to.position; - if (a == b) return; - - Handles.color = Color.white * 0.9f; - Handles.DrawLine(from.position, to.position); - - // Vector2 dir = (b - a).normalized; - // Vector2 start = a + dir * from.radius; - // Vector2 end = b - dir * to.radius; - - //DrawArrowHead(end - dir * 2f, end, 10f / zoom, 12f / zoom, Color.white); - - } - - void DrawArrowHead(Vector2 from, Vector2 to, float headWidth, float headLength, Color color) { - Vector2 dir = (to - from).normalized; - if (dir == Vector2.zero) return; - Vector2 right = new Vector2(-dir.y, dir.x); - - Vector3 p1 = to; - Vector3 p2 = to - dir * headLength + right * headWidth * 0.5f; - Vector3 p3 = to - dir * headLength - right * headWidth * 0.5f; - - Handles.color = color; - Handles.DrawAAConvexPolygon(p1, p2, p3); - } - - // Left-to-right layered layout (sources on the left, sinks on the right) - void ComputeLeftToRightLayout() { - // build adjacency and indegree - var adj = nodes.ToDictionary(n => n.id, n => new List()); - var indeg = nodes.ToDictionary(n => n.id, n => 0); - foreach (var e in edges) { - if (!adj.ContainsKey(e.fromId) || !adj.ContainsKey(e.toId)) continue; - adj[e.fromId].Add(e.toId); - indeg[e.toId]++; - } - - // Kahn's algorithm to compute topological layers (horizontal layers) - Dictionary layer = new(); - Queue q = new(indeg.Where(kv => kv.Value == 0).Select(kv => kv.Key)); - foreach (var id in q) layer[id] = 0; - - while (q.Count > 0) { - int u = q.Dequeue(); - int l = layer[u]; - foreach (var v in adj[u]) { - // prefer placing v at least one layer after u - if (!layer.ContainsKey(v) || layer[v] < l + 1) layer[v] = l + 1; - indeg[v]--; - if (indeg[v] == 0) q.Enqueue(v); - } - } - - // Any unreachable nodes -> assign next layers - int maxLayer = layer.Count > 0 ? layer.Values.Max() : 0; - foreach (var n in nodes) { - if (!layer.ContainsKey(n.id)) { - maxLayer++; - layer[n.id] = maxLayer; - } - } - - // Group nodes by layer (left to right) - var layers = layer.GroupBy(kv => kv.Value).OrderBy(g => g.Key).Select(g => g.Select(x => x.Key).ToList()).ToList(); - - // Layout parameters (horizontal spacing drives left->right) - float hSpacing = 150f; - float vSpacing = 100f; - - // Place nodes: x increases with layer index, y spaced within layer - for (int li = 0; li < layers.Count; li++) { - var lst = layers[li]; - float totalHeight = (lst.Count - 1) * vSpacing; - for (int i = 0; i < lst.Count; i++) { - int id = lst[i]; - var n = GetNodeById(id); - if (n == null) continue; - float x = hSpacing + li * hSpacing; - float y = 400 - totalHeight / 2f + i * vSpacing; - // Debug.Log($"({li}, {i}) -> {x}, {y}"); - n.position = new Vector2(x, y); - } - } - - Repaint(); - } - - void FitToView() { - if (nodes.Count == 0) return; - // compute bounds including radii - Rect bounds = new Rect(nodes[0].position - Vector2.one * nodes[0].radius, Vector2.one * nodes[0].radius * 2f); - foreach (var n in nodes) - bounds = RectUnion(bounds, new Rect(n.position - Vector2.one * n.radius, Vector2.one * n.radius * 2f)); - - // center graph at origin (0,0) then set pan so it appears centered in window - Vector2 graphCenter = bounds.center; - // move nodes so center is at origin - for (int i = 0; i < nodes.Count; i++) - nodes[i].position -= graphCenter; - - // reset pan/zoom so centered - pan = Vector2.zero; - zoom = 1.0f; - Repaint(); - } - - - static Rect RectUnion(Rect a, Rect b) { - float xMin = Mathf.Min(a.xMin, b.xMin); - float xMax = Mathf.Max(a.xMax, b.xMax); - float yMin = Mathf.Min(a.yMin, b.yMin); - float yMax = Mathf.Max(a.yMax, b.yMax); - return Rect.MinMaxRect(xMin, yMin, xMax, yMax); - } - - Vector2 ScreenToGraph_old(Vector2 screenPos) { - Vector2 origin = new Vector2(position.width / 2, position.height / 2); - // invert the GUI.matrix transform (approx for current simple transforms) - return (screenPos - (origin + pan)) / zoom + origin * (1 - 1 / zoom); - } - Vector2 ScreenToGraph(Vector2 screenPos) { - Vector2 windowCenter = new Vector2(position.width / 2f, position.height / 2f); - Rect bounds = GetGraphBounds(); - Vector2 graphCenter = bounds.center; - Vector2 autoPan = -graphCenter; - // inverse of: screen -> translate by -(windowCenter+autoPan+pan), scale by 1/zoom, translate by windowCenter - return (screenPos - (windowCenter + autoPan + pan)) / zoom + windowCenter; - } - - - Rect GetGraphBounds() { - if (nodes == null || nodes.Count == 0) return new Rect(Vector2.zero, Vector2.one); - Rect bounds = new( - nodes[0].position - Vector2.one * nodes[0].radius, - 2f * nodes[0].radius * Vector2.one); - foreach (var n in nodes) - bounds = RectUnion(bounds, - new Rect(n.position - Vector2.one * n.radius, 2f * n.radius * Vector2.one)); - return bounds; - } - - - - int HitTestNode(Vector2 graphPos) { - // returns node id under point or -1 - for (int i = nodes.Count - 1; i >= 0; i--) { - var n = nodes[i]; - if ((graphPos - n.position).sqrMagnitude <= n.radius * n.radius) return n.id; - } - return -1; - } - -} diff --git a/NanoBrain/Editor/BrainEditorWindow.cs.meta b/NanoBrain/Editor/BrainEditorWindow.cs.meta deleted file mode 100644 index 5d8b61f..0000000 --- a/NanoBrain/Editor/BrainEditorWindow.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: f041740900808273ab006e7d276a78e9 diff --git a/NanoBrain/Editor/BrainPickerWindow.cs b/NanoBrain/Editor/BrainPickerWindow.cs deleted file mode 100644 index 503bd10..0000000 --- a/NanoBrain/Editor/BrainPickerWindow.cs +++ /dev/null @@ -1,66 +0,0 @@ -using UnityEditor; -using UnityEngine; -using System; -using System.Linq; - -public class ClusterPickerWindow : EditorWindow { - private Vector2 scroll; - private ClusterPrefab[] items = new ClusterPrefab[0]; - private Action onPicked; - private string search = ""; - - public static void ShowPicker(Action onPicked, string title = "Select Cluster") { - var w = CreateInstance(); - w.titleContent = new GUIContent(title); - w.minSize = new Vector2(360, 320); - w.onPicked = onPicked; - w.RefreshList(); - w.ShowModalUtility(); // modal dialog - } - - private void OnEnable() => RefreshList(); - - private void RefreshList() { - var guids = AssetDatabase.FindAssets("t:ClusterPrefab"); - items = guids - .Select(g => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(g))) - .Where(b => b != null) - .OrderBy(b => b.name) - .ToArray(); - } - - private void OnGUI() { - EditorGUILayout.Space(); - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("Choose Cluster:", EditorStyles.boldLabel); - if (GUILayout.Button("Refresh", GUILayout.Width(80))) RefreshList(); - GUILayout.FlexibleSpace(); - EditorGUILayout.EndHorizontal(); - - EditorGUILayout.Space(); - search = EditorGUILayout.TextField(search); - - EditorGUILayout.Space(); - scroll = EditorGUILayout.BeginScrollView(scroll); - foreach (var it in items) { - if (!string.IsNullOrEmpty(search) && it.name.IndexOf(search, StringComparison.OrdinalIgnoreCase) < 0) - continue; - - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField(EditorGUIUtility.ObjectContent(it, typeof(ClusterPrefab)), GUILayout.Height(20)); - if (GUILayout.Button("Select", GUILayout.Width(70))) { - onPicked?.Invoke(it); - Close(); - return; - } - EditorGUILayout.EndHorizontal(); - } - EditorGUILayout.EndScrollView(); - - EditorGUILayout.Space(); - EditorGUILayout.BeginHorizontal(); - if (GUILayout.Button("Cancel")) { onPicked?.Invoke(null); Close(); } - GUILayout.FlexibleSpace(); - EditorGUILayout.EndHorizontal(); - } -} diff --git a/NanoBrain/Editor/BrainPickerWindow.cs.meta b/NanoBrain/Editor/BrainPickerWindow.cs.meta deleted file mode 100644 index 1468869..0000000 --- a/NanoBrain/Editor/BrainPickerWindow.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 9197e2d322d23b5798ab4aef729815b0 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Editor/ClusterInspector.cs b/NanoBrain/Editor/ClusterInspector.cs deleted file mode 100644 index 2999938..0000000 --- a/NanoBrain/Editor/ClusterInspector.cs +++ /dev/null @@ -1,1100 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using UnityEditor; - -using UnityEngine; -using UnityEngine.UIElements; -using Unity.Mathematics; -using static Unity.Mathematics.math; - -[CustomEditor(typeof(ClusterPrefab))] -public class ClusterInspector : Editor { - protected static VisualElement mainContainer; - protected static VisualElement inspectorContainer; - - protected bool breakOnWake = false; - - #region Start - - public override VisualElement CreateInspectorGUI() { - ClusterPrefab prefab = target as ClusterPrefab; - - if (prefab != null) - prefab.EnsureInitialization(); - - serializedObject.Update(); - - VisualElement root = new(); - CreateInspector(root, prefab, prefab.output, null); - - serializedObject.ApplyModifiedProperties(); - return root; - } - - public static GraphView CreateInspector(VisualElement root, ClusterPrefab cluster, Nucleus output, GameObject gameObject) { - root.style.paddingLeft = 0; - root.style.paddingRight = 0; - root.style.paddingTop = 0; - root.style.paddingBottom = 0; - - root.styleSheets.Add(Resources.Load("GraphStyles")); - - // does the main container have added value? - // is just is like the root - mainContainer = new() { - style = { - height = 450, - flexDirection = FlexDirection.Row - } - }; - GraphView graph = new(cluster); - graph.style.flexGrow = 1; - - inspectorContainer = new VisualElement { - name = "inspector", - style = { - width = 500, - flexGrow = 0 - } - }; - - mainContainer.Add(graph); - mainContainer.Add(inspectorContainer); - root.Add(mainContainer); - - graph.SetGraph(gameObject, output, inspectorContainer); - - return graph; - } - - - public class GraphView : VisualElement { - readonly ClusterPrefab prefab; - SerializedObject serializedBrain; - Nucleus currentNucleus; - GameObject gameObject; - private List layers = new(); - private readonly Dictionary neuroidPositions = new(); - private bool expandArray = false; - - ClusterPrefab prefabAsset; - readonly PopupField outputsField; - - public GraphView(ClusterPrefab prefab) { - this.prefab = prefab; - - name = "content"; - style.flexGrow = 1; - - IMGUIContainer graphContainer = new(OnIMGUI); - graphContainer.style.position = Position.Absolute; - graphContainer.style.left = 0; graphContainer.style.top = 0; - graphContainer.style.right = 0; graphContainer.style.bottom = 0; - graphContainer.pickingMode = PickingMode.Position; - graphContainer.focusable = true; - Add(graphContainer); - - VisualElement outputContainer = new() { - style = { - flexDirection = FlexDirection.Row, - alignItems = Align.Center, - } - }; - - List names = this.prefab.outputs.Select(output => output.name).ToList(); - if (names.Count > 0 && names.First() != null) { - outputsField = new(names, names.First()) { - style = { flexGrow = 1 } - }; - outputsField.RegisterValueChangedCallback(evt => OnOutputChanged(evt.newValue)); - outputContainer.Add(outputsField); - } - - Button addButton = new(() => OnAddClusterOutput()) { - text = "Add" - }; - outputContainer.Add(addButton); - - Add(outputContainer); - - // Subscribe when added to panel (editor UI ready) - RegisterCallback(evt => Subscribe()); - RegisterCallback(evt => Unsubscribe()); - } - - void OnOutputChanged(string outputName) { - if (this.currentNucleus.parent != null) - // Get nucleus in the parent instance - this.currentNucleus = this.currentNucleus.parent.GetNucleus(outputName); - else - // Get nucleus in the prefab - this.currentNucleus = this.prefab.GetNucleus(outputName); - } - - void OnAddClusterOutput() { - Nucleus newOutput = new Neuron(this.prefab, "New Output"); - this.prefab.RefreshOutputs(); - outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); - outputsField.value = newOutput.name; - - this.currentNucleus = newOutput; - } - - bool subscribed = false; - void Subscribe() { - if (subscribed) return; - SceneView.duringSceneGui += OnSceneGUI; - subscribed = true; - SceneView.RepaintAll(); - } - - void Unsubscribe() { - if (!subscribed) return; - SceneView.duringSceneGui -= OnSceneGUI; - subscribed = false; - } - - public void SetGraph(GameObject gameObject, Nucleus nucleus, VisualElement inspectorContainer) { - this.gameObject = gameObject; - //this.cluster = brain; - if (Application.isPlaying == false) - this.serializedBrain = new SerializedObject(this.prefab); - this.currentNucleus = nucleus; - Rebuild(inspectorContainer); - } - - void Rebuild(VisualElement inspectorContainer) { - BuildLayers(); - - if (this.currentNucleus == null) { - inspectorContainer.Clear(); - return; - } - - string path = AssetDatabase.GetAssetPath(this.prefab); // or known path - this.prefabAsset = AssetDatabase.LoadAssetAtPath(path); - if (this.prefabAsset == null) { - // create in memory save if it doesn't exist - this.prefabAsset = CreateInstance(); - //Debug.LogError("Cluster Prefab is not found on disk"); - } - DrawInspector(inspectorContainer); - } - - private void BuildLayers() { - // A temporary list to track what's been added to layers - this.layers = new(); - int layerIx = 0; - - Nucleus selectedNucleus = this.currentNucleus; - if (selectedNucleus == null) - return; - NeuroidLayer currentLayer = new() { ix = layerIx }; - - if (selectedNucleus is Neuron selectedNeuron && selectedNeuron.receivers != null) { - foreach (Nucleus receiver in selectedNeuron.receivers) { - Nucleus outputNeuroid = receiver; - if (outputNeuroid != null) { - AddToLayer(currentLayer, outputNeuroid); - // Debug.Log($"layer {layerIx} nucleus {outputNeuroid.name}"); - } - } - } - if (currentLayer.neuroids.Count > 0) { - this.layers.Add(currentLayer); - layerIx++; - currentLayer = new() { ix = layerIx }; - } - - AddToLayer(currentLayer, selectedNucleus); - this.layers.Add(currentLayer); - // Debug.Log($"layer {layerIx} nucleus {selectedNucleus.name}"); - - layerIx++; - currentLayer = new() { ix = layerIx }; - - if (selectedNucleus.synapses != null) { - foreach (Synapse synapse in selectedNucleus.synapses) { - Nucleus input = synapse.neuron; - AddToLayer(currentLayer, input); - // Debug.Log($"layer {layerIx} nucleus {input.name}"); - } - } - if (currentLayer.neuroids.Count > 0) { - this.layers.Add(currentLayer); - } - } - - private void AddToLayer(NeuroidLayer layer, Nucleus nucleus) { - if (nucleus == null) - return; - layer.neuroids.Add(nucleus); - //nucleus.layerIx = layer.ix; - // Store its position - Vector2Int neuroidPosition = new(layer.ix, layer.neuroids.Count - 1); - neuroidPositions[nucleus] = neuroidPosition; - - } - - - public void OnIMGUI() { - if (currentNucleus == null) - return; - - if (Application.isPlaying == false) - serializedBrain.Update(); - - Handles.BeginGUI(); - DrawGraph(); - Handles.EndGUI(); - - } - - private void DrawGraph() { - float size = 20; - Vector3 position = new(150, 210, 0); - - DrawReceivers(this.currentNucleus, position, size); - DrawSynapses(this.currentNucleus, position, size); - - // Draw selected Nucleus - if (expandArray) { - if (this.currentNucleus is IReceptor receptor1) { - float maxValue = 0; - foreach (Nucleus nucleus in receptor1.nucleiArray) { - if (nucleus is Neuron neuron) { - float value = length(neuron.outputValue); - if (value > maxValue) - maxValue = value; - } - } - - float spacing = 400f / receptor1.nucleiArray.Count(); - float margin = 10 + spacing / 2; - float xMin = 150 - size; - float xMax = 150 + size; - float yMin = 10 + margin - size / 2; - float yMax = 400 - margin + size; - Vector3[] verts = new Vector3[4] { - new(xMin, yMin, 0), - new(xMax, yMin, 0), - new(xMax, yMax, 0), - new(xMin, yMax, 0) - }; - Handles.color = Color.black; - Handles.DrawAAConvexPolygon(verts); - int row = 0; - foreach (Nucleus nucleus in receptor1.nucleiArray) { - Vector3 pos = new(150, margin + row * spacing, 0.0f); - Handles.color = Color.white; - // The selected nucleus highlight ring - Handles.DrawSolidDisc(pos, Vector3.forward, size + 2); - DrawNucleus(nucleus, pos, maxValue, size); - row++; - } - GUIStyle style = new(EditorStyles.label) { - alignment = TextAnchor.UpperCenter, - normal = { textColor = Color.white }, - fontStyle = FontStyle.Bold, - }; - Vector3 labelPos = new(150, yMax + size + 5, 0); - string receptorName = receptor1.GetName(); - int colonPos = receptorName.IndexOf(":"); - if (colonPos > 0) { - string baseName = receptorName[..colonPos]; - Handles.Label(labelPos, baseName, style); - } - else - Handles.Label(labelPos, receptorName, style); - } - else { - Handles.color = Color.white; - // The selected nucleus highlight ring - Handles.DrawSolidDisc(position, Vector3.forward, size + 2); - float maxValue = 1; - if (this.currentNucleus is Neuron neuron) - maxValue = length(neuron.outputValue); - else if (this.currentNucleus is Cluster cluster) - maxValue = length(cluster.defaultOutput.outputValue); - - DrawNucleus(this.currentNucleus, position, maxValue, 20); - - } - } - else { - Handles.color = Color.white; - // The selected nucleus highlight ring - Handles.DrawSolidDisc(position, Vector3.forward, size + 2); - float maxValue = 1; - if (this.currentNucleus is Neuron neuron) - maxValue = length(neuron.outputValue); - else if (this.currentNucleus is Cluster cluster) - maxValue = length(cluster.defaultOutput.outputValue); - DrawNucleus(this.currentNucleus, position, maxValue, 20); - } - } - - private void DrawReceivers(Nucleus nucleus, Vector3 parentPos, float size) { - List receivers; - if (nucleus is Neuron neuron) - receivers = neuron.receivers; - else if (nucleus is Cluster cluster) - receivers = cluster.CollectReceivers(); - else - return; - - int nodeCount = receivers.Count(); //neuron != null ? neuron.receivers.Count() : 1; - - // Determine the maximum value in this layer - // This is used to 'scale' the output value colors of the nuclei - float maxValue = 0; - foreach (Nucleus receiver in receivers) { - if (receiver is Neuron neuroid) { - float value = length(neuroid.outputValue); - if (value > maxValue) - maxValue = value; - } - } - - // Determine the spacing of the nuclei in the layer - float spacing = 400f / nodeCount; - float margin = 10 + spacing / 2; - - int row = 0; - List drawnArrays = new(); - foreach (Nucleus receiver in receivers) { - if (receiver is Receptor receptor) { - if (drawnArrays.Contains(receptor.nucleiArray)) - continue; - drawnArrays.Add(receptor.nucleiArray); - } - - Nucleus receiverNucleus = receiver; - if (receiverNucleus == null) - continue; - - Vector3 pos = new(50, margin + row * spacing, 0.0f); - Handles.color = Color.white; - Handles.DrawLine(parentPos, pos); - - DrawNucleus(receiverNucleus, pos, maxValue, size); - row++; - } - } - - private void DrawSynapses(Nucleus nucleus, Vector3 parentPos, float size) { - int nodeCount = nucleus.synapses.Count; - - // Determine the maximum value in this layer - // This is used to 'scale' the output value colors of the nuclei - float maxValue = 0; - int neuronCount = 0; - List drawnArrays = new(); - foreach (Synapse synapse in nucleus.synapses) { - if (synapse.neuron == null) - continue; - - if (synapse.neuron is Receptor receptor) { - if (drawnArrays.Contains(receptor.nucleiArray)) - continue; - drawnArrays.Add(receptor.nucleiArray); - } - else if (synapse.neuron.parent is ClusterReceptor clusterReceptor) { - if (drawnArrays.Contains(clusterReceptor.nucleiArray)) - continue; - drawnArrays.Add(clusterReceptor.nucleiArray); - } - if (synapse.neuron is Neuron synapseNeuron) { - float value = length(synapseNeuron.outputValue) * synapse.weight; - // Debug.Log($"{synapse.nucleus.name}: {value} {length(synapse.nucleus.outputValue)} {synapse.weight}"); - if (value > maxValue) - maxValue = value; - } - neuronCount++; - } - - // Determine the spacing of the nuclei in the layer - float spacing = 400f / neuronCount; - float margin = 10 + spacing / 2; - - int row = 0; - drawnArrays = new(); - foreach (Synapse synapse in nucleus.synapses) { - if (synapse.neuron is null) - continue; - - if (synapse.neuron is Receptor neuron) { - if (drawnArrays.Contains(neuron.nucleiArray)) - continue; - drawnArrays.Add(neuron.nucleiArray); - } - else if (synapse.neuron.parent is ClusterReceptor clusterReceptor) { - if (drawnArrays.Contains(clusterReceptor.nucleiArray)) - continue; - drawnArrays.Add(clusterReceptor.nucleiArray); - } - Vector3 pos = new(250, margin + row * spacing, 0.0f); - Handles.color = Color.white; - Handles.DrawLine(parentPos, pos); - Color color = Color.black; - if (Application.isPlaying) { - if (maxValue == 0 || !float.IsFinite(maxValue)) - maxValue = 1; - float brightness = 0; - if (synapse.neuron is Neuron synapseNeuron) - brightness = length(synapseNeuron.outputValue * synapse.weight) / maxValue; - color = new Color(brightness, brightness, brightness, 1f); - } - if (synapse.neuron.parent != null && synapse.neuron.parent != this.currentNucleus.parent) { - // the synapse nucleus is part of a subcluster - DrawNucleus(synapse.neuron.parent, pos, maxValue, size, color); - } - // else if (synapse.nucleus.cluster != null && synapse.nucleus.cluster != this.currentNucleus.cluster) { - // DrawNucleus(synapse.nucleus.parent, pos, maxValue, size, color); - // } - else { - DrawNucleus(synapse.neuron, pos, maxValue, size, color); - } - row++; - } - } - - private void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size) { - Color color; - if (Application.isPlaying) { - float brightness = 0; - if (nucleus is Neuron neuron) - brightness = length(neuron.outputValue) / maxValue; - color = new Color(brightness, brightness, brightness, 1f); - } - else - color = Color.black; - DrawNucleus(nucleus, position, maxValue, size, color); - } - - private void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size, Color color) { - if (nucleus is MemoryCell) { - Handles.color = Color.white; - Handles.DrawWireDisc(position + Vector3.right * 10, Vector3.forward, size); - } - - Handles.color = color; - Handles.DrawSolidDisc(position, Vector3.forward, size); - - Handles.color = Color.white; - // Position the label in front of the disc - Vector3 labelPosition = position + (Vector3.forward * 0.1f); - - GUIStyle style = new(EditorStyles.label) { - alignment = TextAnchor.MiddleCenter, - normal = { textColor = Color.white }, - fontStyle = FontStyle.Bold, - }; - - if (nucleus is IReceptor receptor1) { - if (expandArray) { - // Put array indices above elements - style.alignment = TextAnchor.LowerCenter; - Vector3 labelPos1 = position + Vector3.down * (size + 5); // below disc - int colonPos1 = nucleus.name.IndexOf(":"); - if (colonPos1 > 0) { - string extName = nucleus.name[(colonPos1 + 2)..]; - Handles.Label(labelPos1, extName, style); - } - } - else { - // draw the array size label - if (color.grayscale > 0.5f) - style.normal.textColor = Color.black; - else - style.normal.textColor = Color.white; - Handles.Label(labelPosition, receptor1.nucleiArray.Length.ToString(), style); - style.normal.textColor = Color.white; - } - } - - if (expandArray == false || nucleus is not IReceptor) { - // put name below nucleus - Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron - style.alignment = TextAnchor.UpperCenter; - - int colonPos = nucleus.name.IndexOf(":"); - if (colonPos > 0 && colonPos < nucleus.name.Length - 2) { - // if it is an array, we should not show the :0 of the first element - string baseName = nucleus.name[..colonPos]; - Handles.Label(labelPos, baseName, style); - } - else - Handles.Label(labelPos, nucleus.name, style); - - } - - // Draw Cluster ring - if (nucleus is Cluster) { - Handles.color = Color.white; - Handles.DrawWireDisc(position, Vector3.forward, size + 5); - } - - // Tooltip - Rect neuronRect = new(position.x - size, position.y - size, size * 2, size * 2); - int id = GUIUtility.GetControlID(FocusType.Passive); - Event e = Event.current; - EventType et = e.GetTypeForControl(id); - if (e != null && neuronRect.Contains(e.mousePosition)) { - // Process Hover - HandleMouseHover(nucleus, neuronRect); - // Process click - if (e.type == EventType.MouseDown && e.button == 0) { - // Consume the event so the scene doesn't also handle it - e.Use(); - HandleClicked(nucleus); - } - } - } - - private void HandleMouseHover(Nucleus nucleus, Rect rect) { - GUIContent tooltip; - if (nucleus is Neuron neuron) { - tooltip = new( - $"{nucleus.name}" + - $"\nValue: {length(neuron.outputValue)}"); - } - else - tooltip = new($"{nucleus.name}"); - - Vector2 mousePosition = Event.current.mousePosition; - - // Display tooltip with some offset - Vector2 tooltipSize = GUI.skin.box.CalcSize(tooltip); - Rect tooltipRect = new Rect(mousePosition.x + 10, mousePosition.y + 10, tooltipSize.x, tooltipSize.y); - - GUI.Box(tooltipRect, tooltip); - } - - private void HandleClicked(Nucleus nucleus) { - if (nucleus == this.currentNucleus) { - if (nucleus is Receptor || nucleus is ClusterReceptor) - expandArray = !expandArray; - else - expandArray = false; - } - // else if (nucleus is ReceptorInstance receptor) { - // this.currentNucleus = receptor.receptor; - // expandArray = false; - // BuildLayers(); - // } - else { - this.currentNucleus = nucleus; - expandArray = false; - BuildLayers(); - } - } - - private VisualElement inspectorIMGUIContainer; - private bool showSynapses = true; - private bool showActivation = true; - protected bool breakOnWake = false; - protected bool trace = false; - void DrawInspector(VisualElement inspectorContainer) { - if (inspectorContainer == null) - return; - - inspectorContainer.Clear(); - if (this.currentNucleus == null) - return; - - // create a SerializedObject wrapper so Unity inspector controls work (and Undo) - SerializedObject so = new(prefabAsset); - this.inspectorIMGUIContainer = new IMGUIContainer(() => InspectorHandler(so)); - - inspectorContainer.Add(inspectorIMGUIContainer); - } - - void InspectorHandler(SerializedObject serializedObject) { - bool anythingChanged = false; - - if (serializedObject == null || serializedObject.targetObject == null) - return; - - if (this.currentNucleus == null) - return; - - serializedObject.Update(); - - GUIStyle headerStyle = new(EditorStyles.boldLabel) { - alignment = TextAnchor.MiddleLeft, - margin = new RectOffset(10, 0, 4, 4) - }; - GUIStyle boldTextFieldStyle = new(EditorStyles.textField) { - fontStyle = FontStyle.Bold - }; - - GUILayout.Label(this.currentNucleus.GetType().ToString(), headerStyle); - string newName = EditorGUILayout.TextField(this.currentNucleus.name, boldTextFieldStyle); - if (newName != this.currentNucleus.name) { - this.currentNucleus.name = newName; - this.prefab.RefreshOutputs(); - outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); - anythingChanged = true; - } - - if (Application.isPlaying) { - if (currentNucleus is Neuron currentNeuron1) { - GUIContent nameLabel = new("Output", currentNeuron1.outputValue.ToString()); - EditorGUILayout.FloatField(nameLabel, length(currentNeuron1.outputValue)); - } - else - EditorGUILayout.LabelField(" "); - } - else - EditorGUILayout.LabelField(" "); - - if (this.currentNucleus is MemoryCell memory) { - memory.staticMemory = EditorGUILayout.Toggle("Static Memory", memory.staticMemory); - } - - if (this.currentNucleus is IReceptor receptor1) { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.IntField("Array size", receptor1.nucleiArray.Count()); - if (GUILayout.Button("Add")) { - Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name); - receptor1.AddReceptorElement(this.prefab); - anythingChanged = true; - } - if (GUILayout.Button("Del")) { - Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name); - receptor1.RemoveReceptorElement(); - anythingChanged = true; - } - EditorGUILayout.EndHorizontal(); - } - - // Synapses - - if (this.currentNucleus is not Receptor && this.currentNucleus is not ClusterReceptor) { - showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses"); - if (showSynapses) { - if (this.currentNucleus is Neuron neuron2) { - Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator); - anythingChanged |= newCombinator != neuron2.combinator; - neuron2.combinator = newCombinator; - } - - EditorGUIUtility.wideMode = true; - EditorGUIUtility.labelWidth = 100; - Vector3 newBias = EditorGUILayout.Vector3Field("Bias", this.currentNucleus.bias); - anythingChanged |= newBias != this.currentNucleus.bias; - this.currentNucleus.bias = newBias; - - Nucleus[] array = null; - int elementIx = -1; - if (this.currentNucleus.synapses.Count > 0) { - Synapse[] synapses = this.currentNucleus.synapses.ToArray(); - foreach (Synapse synapse in synapses) { - if (synapse.neuron == null) - continue; - - if (array != null) { - if (synapse.neuron.parent is Cluster iCluster && elementIx > 0) { - int thisElementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron); - if (thisElementIx == elementIx) - continue; - else - elementIx = thisElementIx; - } - // if (array.Contains(synapse.nucleus)) - // continue; - else if (array.Contains(synapse.neuron.parent)) - continue; - } - else { - if (synapse.neuron.parent is IReceptor iReceptor) { - array = iReceptor.nucleiArray; - if (iReceptor is Cluster iCluster) - elementIx = Cluster.GetNucleusIndex(iCluster.clusterNuclei, synapse.neuron); - } - // else if (synapse.nucleus is Receptor receptor2) // && receptor2.array != null && receptor2.array.nuclei.Length > 1) - // array = receptor2.nucleiArray; - } - - EditorGUILayout.Space(); - - if (Application.isPlaying) { - if (synapse.neuron is Neuron synapseNeuron) { - Vector3 value = synapseNeuron.outputValue * synapse.weight; - GUIContent synapseValueLabel = new(synapse.neuron.name, synapseNeuron.outputValue.ToString()); - EditorGUILayout.FloatField(synapseValueLabel, length(synapseNeuron.outputValue)); - } - } - else { - EditorGUILayout.BeginHorizontal(); - - if (synapse.neuron.parent != null && synapse.neuron.parent != this.currentNucleus) { - // If it is a cluster - GUIStyle labelStyle = new(GUI.skin.label); - float labelWidth = 200; - if (synapse.neuron.clusterPrefab != null) { - labelWidth = labelStyle.CalcSize(new GUIContent($"{synapse.neuron.parent.baseName}.")).x; - GUILayout.Label($"{synapse.neuron.parent.baseName}", GUILayout.Width(labelWidth)); - } - string[] options = synapse.neuron.parent.clusterNuclei.Select(n => n.name).ToArray(); - int selectedIndex = System.Array.IndexOf(options, synapse.neuron.name); - int newIndex = EditorGUILayout.Popup(selectedIndex, options); - if (newIndex != selectedIndex && synapse.neuron.parent.clusterNuclei[newIndex] is Neuron newNeuron) - ChangeSynapse(synapse, newNeuron); - } - else - GUILayout.Label(synapse.neuron.name); - - bool disconnecting = GUILayout.Button("Disconnect", GUILayout.Width(80)); - if (disconnecting && synapse.neuron is Neuron synapseNeuron) { - synapseNeuron.RemoveReceiver(this.currentNucleus); - this.prefab.GarbageCollection(); - anythingChanged = true; - } - EditorGUILayout.EndHorizontal(); - - } - - EditorGUI.indentLevel++; - float newWeight = EditorGUILayout.FloatField("Weight", synapse.weight); - if (newWeight != synapse.weight) { - if (synapse.neuron.parent is IReceptor receptor) { - Nucleus[] receptorArray = receptor.nucleiArray; - foreach (Synapse s in this.currentNucleus.synapses) { - if (s.neuron.parent is IReceptor r && r.nucleiArray == receptorArray) - s.weight = newWeight; - } - } - else - synapse.weight = newWeight; - anythingChanged = true; - } - EditorGUI.indentLevel--; - } - } - - EditorGUILayout.Space(); - anythingChanged |= ConnectNucleus(this.prefab, this.currentNucleus); - anythingChanged |= AddSynapse(this.prefab, this.currentNucleus); - } - EditorGUILayout.EndFoldoutHeaderGroup(); - } - - // Activation - - if (this.currentNucleus is not Cluster) { - EditorGUILayout.Space(); - showActivation = EditorGUILayout.BeginFoldoutHeaderGroup(showActivation, "Activation"); - if (showActivation) { - if (this.currentNucleus is Neuron neuron) { - if (this.currentNucleus is not MemoryCell) { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("Activation Curve", GUILayout.Width(150)); - if (neuron.curveMax > 0) - EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, 0, 1, neuron.curveMax)); - else - EditorGUILayout.CurveField(neuron.curve, Color.cyan, new Rect(0, neuron.curveMax, 1, -neuron.curveMax)); - Neuron.CurvePresets newPreset = (Neuron.CurvePresets)EditorGUILayout.EnumPopup(neuron.curvePreset, GUILayout.Width(100)); - anythingChanged |= newPreset != neuron.curvePreset; - neuron.curvePreset = newPreset; - EditorGUILayout.EndHorizontal(); - } - if (neuron is Receptor receptor2) { - if (receptor2.nucleiArray == null || receptor2.nucleiArray.Count() == 0) - receptor2.array = new NucleusArray(neuron); - } - } - - EditorGUILayout.Space(); - } - EditorGUILayout.EndFoldoutHeaderGroup(); - } - - if (GUILayout.Button("Delete this neuron")) - DeleteNucleus(this.currentNucleus); - - if (this.currentNucleus is Cluster subCluster) { - if (GUILayout.Button("Edit Cluster")) - EditCluster(subCluster); - } - - EditorGUILayout.Space(); - breakOnWake = EditorGUILayout.Toggle("Break on wake", breakOnWake); - if (breakOnWake && this.currentNucleus is Neuron currentNeuron) { - if (currentNeuron.isSleeping == false) - Debug.Break(); - } - trace = EditorGUILayout.Toggle("Trace", trace); - this.currentNucleus.trace = trace; - - serializedObject.ApplyModifiedProperties(); - if (anythingChanged) { - EditorUtility.SetDirty(prefabAsset); - AssetDatabase.SaveAssets(); - } - } - - void OnSceneGUI(SceneView sceneView) { - if (this.gameObject != null) { - if (this.currentNucleus is IReceptor receptor) { - foreach (Nucleus nucleus in receptor.nucleiArray) { - if (nucleus is Neuron neuron) { - Vector3 worldVector = this.gameObject.transform.TransformVector(neuron.outputValue); - Handles.color = Color.yellow; - Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); - } - } - } - else { - if (this.currentNucleus is Neuron currentNeuron) { - Vector3 worldVector = this.gameObject.transform.TransformVector(currentNeuron.outputValue); - Handles.color = Color.yellow; - Handles.DrawLine(this.gameObject.transform.position, this.gameObject.transform.position + worldVector); - } - } - } - } - - #region Synapses - - protected virtual void AddInput(Nucleus.Type selectedType, Nucleus nucleus) { - switch (selectedType) { - case Nucleus.Type.Neuron: - AddNeuronInput(nucleus); - break; - case Nucleus.Type.MemoryCell: - AddMemoryCellInput(nucleus); - break; - // case Nucleus.Type.Selector: - // AddSelectorInput(nucleus); - // break; - case Nucleus.Type.Cluster: - AddClusterInput(nucleus); - break; - // case Nucleus.Type.Pulsar: - // AddPulsarInput(nucleus); - // break; - case Nucleus.Type.Receptor: - AddReceptorInput(nucleus); - break; - // case Nucleus.Type.ReceptorArray: - // AddReceptorArrayInput(nucleus); - // break; - case Nucleus.Type.ClusterReceptor: - AddClusterReceptorInput(nucleus); - break; - default: - break; - } - } - - protected virtual void AddNeuronInput(Nucleus nucleus) { - Neuron newNeuroid = new(this.prefab, "New neuron"); - newNeuroid.AddReceiver(nucleus); - this.currentNucleus = newNeuroid; - BuildLayers(); - } - - // protected void AddSelectorInput(Nucleus nucleus) { - // Selector newSelector = new(this.prefab, "New Selector"); - // newSelector.AddReceiver(nucleus); - // this.currentNucleus = newSelector; - // BuildLayers(); - // } - - // protected void AddPulsarInput(Nucleus nucleus) { - // Pulsar newPulsar = new(this.prefab, "New Pulsar"); - // newPulsar.AddReceiver(nucleus); - // this.currentNucleus = newPulsar; - // BuildLayers(); - // } - - protected virtual void AddMemoryCellInput(Nucleus nucleus) { - MemoryCell newMemory = new(this.prefab, "New memory cell"); - newMemory.AddReceiver(nucleus); - this.currentNucleus = newMemory; - BuildLayers(); - } - - protected virtual void AddClusterInput(Nucleus nucleus) { - ClusterPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster"); - } - private void OnClusterPicked(Nucleus nucleus, ClusterPrefab prefab) { - Cluster subclusterInstance = new(prefab, this.prefab); - subclusterInstance.defaultOutput.AddReceiver(nucleus); - } - - protected virtual void AddReceptorInput(Nucleus nucleus) { - Receptor newReceptor = new(this.prefab, "New Receptor"); - newReceptor.AddReceiver(nucleus); - this.currentNucleus = newReceptor; - BuildLayers(); - } - - protected virtual void AddClusterReceptorInput(Nucleus nucleus) { - ClusterPickerWindow.ShowPicker(prefab => OnClusterReceptorPicked(nucleus, prefab), "Select Cluster"); - } - private void OnClusterReceptorPicked(Nucleus nucleus, ClusterPrefab selectedPrefab) { - ClusterReceptor clusterInstance = new(selectedPrefab, this.prefab, "New " + selectedPrefab.name); - clusterInstance.defaultOutput.AddReceiver(nucleus); - this.currentNucleus = clusterInstance; - BuildLayers(); - } - - private void EditCluster(Cluster subCluster) { - // May be used with storedPrefab... - Selection.activeObject = subCluster.prefab; - EditorGUIUtility.PingObject(subCluster.prefab); - var editor = Editor.CreateEditor(subCluster.prefab); - } - - int selectedConnectNucleus = -1; - // Connect to another nucleus in the same cluster - protected virtual bool ConnectNucleus(ClusterPrefab cluster, Nucleus nucleusToConnect) { - if (cluster == null) - return false; - - IEnumerable synapseNuclei = this.currentNucleus.synapses - .Where(synapse => synapse.neuron != null) - .Select(synapse => synapse.neuron); - - IEnumerable nuclei = cluster.nuclei - .Except(synapseNuclei); - IEnumerable nucleiNames = nuclei - .Select(n => { - int idx = n.name.IndexOf(':'); - return idx < 0 ? n.name : n.name[..idx]; - }) - .Distinct(); - - string[] names = nucleiNames.ToArray(); - EditorGUILayout.BeginHorizontal(); - selectedConnectNucleus = EditorGUILayout.Popup(selectedConnectNucleus, names); - bool connecting = GUILayout.Button("Connect", GUILayout.Width(80)); - EditorGUILayout.EndHorizontal(); - if (connecting) { - Nucleus nucleus = nuclei.ElementAt(selectedConnectNucleus); - if (nucleus is IReceptor receptor) - receptor.AddArrayReceiver(this.currentNucleus); - else if (nucleus is Neuron neuron) - neuron.AddReceiver(this.currentNucleus); - else if (nucleus is Cluster subCluster) - subCluster.defaultOutput.AddReceiver(this.currentNucleus); - - } - return connecting; - } - - protected virtual void DeleteNucleus(Nucleus nucleus) { - if (nucleus == null) - return; - - if (nucleus is Neuron neuron) { - foreach (Nucleus receiver in neuron.receivers) { - if (receiver != null) { - this.currentNucleus = receiver; - break; - } - } - } - this.prefab.nuclei.Remove(nucleus); - - if (outputsField.value == nucleus.name) { - this.prefab.RefreshOutputs(); - outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); - outputsField.index = 0; - } - - Neuron.Delete(nucleus); - - this.currentNucleus = this.prefab.output; - BuildLayers(); - } - - Nucleus.Type selectedType = Nucleus.Type.None; - protected virtual bool AddSynapse(ClusterPrefab cluster, Nucleus nucleus) { - if (cluster == null) - return false; - - EditorGUILayout.BeginHorizontal(); - selectedType = (Nucleus.Type)EditorGUILayout.EnumPopup(selectedType); - bool connecting = GUILayout.Button("Add", GUILayout.Width(80)); - EditorGUILayout.EndHorizontal(); - - if (connecting) { - AddInput(selectedType, this.currentNucleus); - } - return connecting; - // if (selectedType == Nucleus.Type.None) - // return false; - - // AddInput(selectedType, this.currentNucleus); - // return true; - } - - protected virtual void ChangeSynapse(Synapse synapse, Neuron newNucleus) { - Neuron synapseNeuron = synapse.neuron as Neuron; - if (synapse.neuron.parent is Cluster subCluster && subCluster.prefab != this.prefab) { - if (synapse.neuron.parent is ClusterReceptor receptor) { - // the new nucleus is part of a (cluster) receptor, - // so we have to change all synapses to this nucleus array elements - int oldNucleusIx = Cluster.GetNucleusIndex(subCluster.clusterNuclei, synapse.neuron); - int newNucleusIx = Cluster.GetNucleusIndex(subCluster.clusterNuclei, newNucleus); - foreach (Nucleus element in receptor.nucleiArray) { - if (element is not ClusterReceptor clusterReceptor) - continue; - // Get the same neuron as the synapse.nucleus in a different element - // of the ClusterReceptor array - Nucleus oldElementNucleus = clusterReceptor.clusterNuclei[oldNucleusIx]; - if (oldElementNucleus is not Neuron oldElementNeuron) - continue; - // Get the same neuron as newNucleus in a different element - // of the ClusterReceptor array - Nucleus newElementNucleus = clusterReceptor.clusterNuclei[newNucleusIx]; - if (newElementNucleus is not Neuron newElementNeuron) - continue; - - oldElementNeuron.RemoveReceiver(this.currentNucleus); - newElementNeuron.AddReceiver(this.currentNucleus); - // Now find the synapse which pointed to the old Neuron - // Synapse synapseForUpdate = this.currentNucleus.GetSynapse(oldElementNeuron); - // synapseForUpdate.nucleus = newElementNeuron; - } - } - else { - // it is a neuron in a subcluster - synapseNeuron.RemoveReceiver(this.currentNucleus); - newNucleus.AddReceiver(this.currentNucleus); - } - } - else { - synapseNeuron.RemoveReceiver(this.currentNucleus); - newNucleus.AddReceiver(this.currentNucleus); - } - } - - protected virtual void DisconnectNucleus(Neuron nucleus) { - if (this.currentNucleus.clusterPrefab == null) - return; - string[] names = this.currentNucleus.synapses.Select(synapse => synapse.neuron.name).ToArray(); - int selectedIndex = -1; - selectedIndex = EditorGUILayout.Popup("Disconnect from", selectedIndex, names); - if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.clusterPrefab.nuclei.Count) { - Synapse synapse = this.currentNucleus.synapses[selectedIndex]; - Neuron synapseNeuron = synapse.neuron as Neuron; - synapseNeuron.RemoveReceiver(this.currentNucleus); - } - } - - #endregion Synapses - } - - #endregion Start - -} - -public class NeuroidLayer { - public int ix = 0; - public List neuroids = new(); -} diff --git a/NanoBrain/Editor/ClusterInspector.cs.meta b/NanoBrain/Editor/ClusterInspector.cs.meta deleted file mode 100644 index a933254..0000000 --- a/NanoBrain/Editor/ClusterInspector.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 1fc1fb7db9f7ad54a87d31313e7f457d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Editor/DAGWindow.cs b/NanoBrain/Editor/DAGWindow.cs deleted file mode 100644 index aaf5aa3..0000000 --- a/NanoBrain/Editor/DAGWindow.cs +++ /dev/null @@ -1,393 +0,0 @@ - -using UnityEngine; -using UnityEditor; -using System.Collections.Generic; -using System.Linq; - -// Simple DAG data model -// [System.Serializable] -// public class DagNode -// { -// public int id; -// public string title; -// public Vector2 position; -// public float radius = 36f; // circle radius -// } - -// [System.Serializable] -// public class DagEdge -// { -// public int fromId; -// public int toId; -// } - -public class DAGEditorWindow : EditorWindow -{ - List nodes = new List(); - List edges = new List(); - - Vector2 pan = Vector2.zero; - float zoom = 1.0f; - const float minZoom = 0.5f; - const float maxZoom = 2.0f; - - GUIStyle labelStyle; - int selectedNodeId = -1; - - Vector2 dragStart; - bool draggingNode = false; - int draggingNodeId = -1; - - [MenuItem("Window/DAG Viewer (LR, Circles)")] - public static void ShowWindow() - { - var w = GetWindow("DAG Viewer (LR)"); - w.minSize = new Vector2(500, 300); - } - - void OnEnable() - { - labelStyle = new GUIStyle(EditorStyles.label); - labelStyle.alignment = TextAnchor.MiddleCenter; - labelStyle.normal.textColor = Color.white; - labelStyle.fontStyle = FontStyle.Bold; - - if (nodes.Count == 0) - CreateSampleGraph(); - - ComputeLeftToRightLayout(); - } - - void CreateSampleGraph() - { - nodes.Clear(); - edges.Clear(); - - nodes.Add(new DagNode() { id = 0, title = "In1" }); - nodes.Add(new DagNode() { id = 1, title = "In2" }); - nodes.Add(new DagNode() { id = 2, title = "A" }); - nodes.Add(new DagNode() { id = 3, title = "B" }); - nodes.Add(new DagNode() { id = 4, title = "C" }); - nodes.Add(new DagNode() { id = 5, title = "Out1" }); - nodes.Add(new DagNode() { id = 6, title = "Out2" }); - - edges.Add(new DagEdge() { fromId = 0, toId = 2 }); - edges.Add(new DagEdge() { fromId = 1, toId = 2 }); - edges.Add(new DagEdge() { fromId = 2, toId = 3 }); - edges.Add(new DagEdge() { fromId = 2, toId = 4 }); - edges.Add(new DagEdge() { fromId = 3, toId = 5 }); - edges.Add(new DagEdge() { fromId = 4, toId = 6 }); - } - - void OnGUI() - { - HandleInput(); - - Rect rect = new Rect(0, 0, position.width, position.height); - EditorGUI.DrawRect(rect, new Color(0.11f, 0.11f, 0.11f)); - - Matrix4x4 oldMatrix = GUI.matrix; - Vector2 origin = new Vector2(position.width / 2, position.height / 2); - GUI.matrix = Matrix4x4.TRS(origin + pan, Quaternion.identity, Vector3.one * zoom) * - Matrix4x4.TRS(-origin, Quaternion.identity, Vector3.one); - - // Draw edges first - foreach (var e in edges) - { - var from = GetNodeById(e.fromId); - var to = GetNodeById(e.toId); - if (from == null || to == null) continue; - DrawEdgeCircleNodes(from, to); - } - - // Draw nodes (circles) - foreach (var n in nodes) - { - DrawNodeCircle(n); - } - - GUI.matrix = oldMatrix; - - // Footer toolbar - GUILayout.FlexibleSpace(); - EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); - if (GUILayout.Button("Fit", EditorStyles.toolbarButton)) FitToView(); - if (GUILayout.Button("Layout LR", EditorStyles.toolbarButton)) ComputeLeftToRightLayout(); - if (GUILayout.Button("Add Node", EditorStyles.toolbarButton)) - { - AddNode("N" + nodes.Count); - ComputeLeftToRightLayout(); - } - if (GUILayout.Button("Add Edge (selected->new)", EditorStyles.toolbarButton)) - { - if (selectedNodeId != -1) - { - var newNode = AddNode("N" + nodes.Count); - edges.Add(new DagEdge() { fromId = selectedNodeId, toId = newNode.id }); - ComputeLeftToRightLayout(); - } - } - EditorGUILayout.EndHorizontal(); - } - - void HandleInput() - { - Event e = Event.current; - - // Zoom with scroll - if (e.type == EventType.ScrollWheel) - { - float oldZoom = zoom; - float delta = -e.delta.y * 0.01f; - zoom = Mathf.Clamp(zoom + delta, minZoom, maxZoom); - Vector2 mouse = e.mousePosition; - pan += (mouse - new Vector2(position.width / 2, position.height / 2)) * (1 - zoom / oldZoom); - e.Use(); - } - - // Pan with middle or right+ctrl drag - if (e.type == EventType.MouseDrag && (e.button == 2 || (e.button == 1 && e.control))) - { - pan += e.delta; - e.Use(); - } - - // Node dragging & selection (convert mouse to graph space) - Vector2 graphMouse = ScreenToGraph(e.mousePosition); - if (e.type == EventType.MouseDown && e.button == 0) - { - int hit = HitTestNode(graphMouse); - if (hit != -1) - { - selectedNodeId = hit; - draggingNode = true; - draggingNodeId = hit; - dragStart = graphMouse; - e.Use(); - } - else - { - selectedNodeId = -1; - } - } - - if (draggingNode && draggingNodeId != -1) - { - if (e.type == EventType.MouseDrag && e.button == 0) - { - Vector2 graphDelta = e.delta / zoom; - var n = GetNodeById(draggingNodeId); - if (n != null) - { - n.position += graphDelta; - Repaint(); - e.Use(); - } - } - if (e.type == EventType.MouseUp && e.button == 0) - { - draggingNode = false; - draggingNodeId = -1; - e.Use(); - } - } - } - - DagNode AddNode(string title) - { - int nextId = nodes.Count > 0 ? nodes.Max(n => n.id) + 1 : 0; - var n = new DagNode() { id = nextId, title = title, position = Vector2.zero }; - nodes.Add(n); - return n; - } - - DagNode GetNodeById(int id) => nodes.FirstOrDefault(x => x.id == id); - - void DrawNodeCircle(DagNode n) - { - Vector2 center = n.position; - float r = n.radius; - Rect nodeRect = new Rect(center.x - r, center.y - r, r * 2, r * 2); - - // circle background - Color bg = (n.id == selectedNodeId) ? new Color(0.15f, 0.5f, 0.9f) : new Color(0.2f, 0.2f, 0.2f); - EditorGUI.DrawRect(nodeRect, bg); - - // anti-aliased circle outline - Handles.color = Color.white * 0.9f; - Handles.DrawAAPolyLine(3f / zoom, GetCircleOutlinePoints(center, r, 48).ToArray()); - - // label - Vector2 labelPos = center - new Vector2(0, 8); - GUI.Label(new Rect(labelPos.x - r, labelPos.y - 8, r * 2, 18), n.title, labelStyle); - } - - List GetCircleOutlinePoints(Vector2 center, float radius, int segments) - { - var pts = new List(segments + 1); - for (int i = 0; i <= segments; i++) - { - float a = (float)i / segments * Mathf.PI * 2f; - pts.Add(new Vector3(center.x + Mathf.Cos(a) * radius, center.y + Mathf.Sin(a) * radius, 0)); - } - return pts; - } - - void DrawEdgeCircleNodes(DagNode from, DagNode to) - { - Vector2 a = from.position; - Vector2 b = to.position; - if (a == b) return; - - // Compute edge line that starts/ends at circle circumferences - Vector2 dir = (b - a).normalized; - Vector2 start = a + dir * from.radius; - Vector2 end = b - dir * to.radius; - - // Use a simple curved line: start -> control -> end (bezier) - Vector2 control = new Vector2((start.x + end.x) / 2f, (start.y + end.y) / 2f); - // Slight vertical offset to separate overlapping lines based on node ids - float offset = ((from.id * 7 + to.id * 11) % 7 - 3) * 6f / zoom; - control += new Vector2(0, offset); - - Handles.color = Color.white * 0.9f; - Handles.DrawAAPolyLine(3f / zoom, 20, GetBezierPoints(start, control, end, 24).ToArray()); - - // Arrow at end pointing towards 'b' - DrawArrowHead(end - dir * 2f, end, 10f / zoom, 12f / zoom, Color.white); - } - - List GetBezierPoints(Vector2 p0, Vector2 p1, Vector2 p2, int seg) - { - var pts = new List(seg + 1); - for (int i = 0; i <= seg; i++) - { - float t = (float)i / seg; - Vector2 p = (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2; - pts.Add(new Vector3(p.x, p.y, 0)); - } - return pts; - } - - void DrawArrowHead(Vector2 from, Vector2 to, float headWidth, float headLength, Color color) - { - Vector2 dir = (to - from).normalized; - if (dir == Vector2.zero) return; - Vector2 right = new Vector2(-dir.y, dir.x); - - Vector3 p1 = to; - Vector3 p2 = to - dir * headLength + right * headWidth * 0.5f; - Vector3 p3 = to - dir * headLength - right * headWidth * 0.5f; - - Handles.color = color; - Handles.DrawAAConvexPolygon(p1, p2, p3); - } - - // Left-to-right layered layout (sources on the left, sinks on the right) - void ComputeLeftToRightLayout() - { - // build adjacency and indegree - var adj = nodes.ToDictionary(n => n.id, n => new List()); - var indeg = nodes.ToDictionary(n => n.id, n => 0); - foreach (var e in edges) - { - if (!adj.ContainsKey(e.fromId) || !adj.ContainsKey(e.toId)) continue; - adj[e.fromId].Add(e.toId); - indeg[e.toId]++; - } - - // Kahn's algorithm to compute topological layers (horizontal layers) - Dictionary layer = new Dictionary(); - Queue q = new Queue(indeg.Where(kv => kv.Value == 0).Select(kv => kv.Key)); - foreach (var id in q) layer[id] = 0; - - while (q.Count > 0) - { - int u = q.Dequeue(); - int l = layer[u]; - foreach (var v in adj[u]) - { - // prefer placing v at least one layer after u - if (!layer.ContainsKey(v) || layer[v] < l + 1) layer[v] = l + 1; - indeg[v]--; - if (indeg[v] == 0) q.Enqueue(v); - } - } - - // Any unreachable nodes -> assign next layers - int maxLayer = layer.Count > 0 ? layer.Values.Max() : 0; - foreach (var n in nodes) - { - if (!layer.ContainsKey(n.id)) - { - maxLayer++; - layer[n.id] = maxLayer; - } - } - - // Group nodes by layer (left to right) - var layers = layer.GroupBy(kv => kv.Value).OrderBy(g => g.Key).Select(g => g.Select(x => x.Key).ToList()).ToList(); - - // Layout parameters (horizontal spacing drives left->right) - float hSpacing = 220f; - float vSpacing = 120f; - - // Place nodes: x increases with layer index, y spaced within layer - for (int li = 0; li < layers.Count; li++) - { - var lst = layers[li]; - float totalHeight = (lst.Count - 1) * vSpacing; - for (int i = 0; i < lst.Count; i++) - { - int id = lst[i]; - var n = GetNodeById(id); - if (n == null) continue; - float x = li * hSpacing; - float y = -totalHeight / 2f + i * vSpacing; - n.position = new Vector2(x, y); - } - } - - Repaint(); - } - - void FitToView() - { - if (nodes.Count == 0) return; - Rect bounds = new Rect(nodes[0].position - Vector2.one * nodes[0].radius, Vector2.one * nodes[0].radius * 2f); - foreach (var n in nodes) - bounds = RectUnion(bounds, new Rect(n.position - Vector2.one * n.radius, Vector2.one * n.radius * 2f)); - - Vector2 center = bounds.center; - pan = -center; - zoom = 1.0f; - Repaint(); - } - - static Rect RectUnion(Rect a, Rect b) - { - float xMin = Mathf.Min(a.xMin, b.xMin); - float xMax = Mathf.Max(a.xMax, b.xMax); - float yMin = Mathf.Min(a.yMin, b.yMin); - float yMax = Mathf.Max(a.yMax, b.yMax); - return Rect.MinMaxRect(xMin, yMin, xMax, yMax); - } - - Vector2 ScreenToGraph(Vector2 screenPos) - { - Vector2 origin = new Vector2(position.width / 2, position.height / 2); - // invert the GUI.matrix transform (approx for current simple transforms) - return (screenPos - (origin + pan)) / zoom + origin * (1 - 1 / zoom); - } - - int HitTestNode(Vector2 graphPos) - { - // returns node id under point or -1 - for (int i = nodes.Count - 1; i >= 0; i--) - { - var n = nodes[i]; - if ((graphPos - n.position).sqrMagnitude <= n.radius * n.radius) return n.id; - } - return -1; - } -} diff --git a/NanoBrain/Editor/DAGWindow.cs.meta b/NanoBrain/Editor/DAGWindow.cs.meta deleted file mode 100644 index 8765c5a..0000000 --- a/NanoBrain/Editor/DAGWindow.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 95393aed582b8b30d965400672aec4d8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Editor/NanoBrain_Editor.cs b/NanoBrain/Editor/NanoBrain_Editor.cs deleted file mode 100644 index 25572d7..0000000 --- a/NanoBrain/Editor/NanoBrain_Editor.cs +++ /dev/null @@ -1,50 +0,0 @@ -using UnityEditor; -using UnityEditor.UIElements; - -using UnityEngine; -using UnityEngine.UIElements; - -[CustomEditor(typeof(NanoBrain))] -public class NanoBrainComponent_Editor : Editor { - protected static VisualElement mainContainer; - protected static VisualElement inspectorContainer; - - protected NanoBrain component; - // private SerializedProperty brainProp; - - // ClusterInspector.GraphView board; - - public void OnEnable() { - component = target as NanoBrain; - - // if (Application.isPlaying == false) { - // string propertyName = nameof(NanoBrain.defaultBrain); - // brainProp = serializedObject.FindProperty(propertyName); - // } - } - - public override VisualElement CreateInspectorGUI() { - Cluster brain = component.brain; - - if (Application.isPlaying == false) - serializedObject.Update(); - - - VisualElement root = new(); - if (Application.isPlaying == false) { - SerializedProperty brainProp = serializedObject.FindProperty(nameof(NanoBrain.defaultBrain)); - PropertyField brainField = new(brainProp) { - label = "Nano Brain" - }; - root.Add(brainField); - } - - if (brain != null) - ClusterInspector.CreateInspector(root, brain.prefab, brain.defaultOutput, component.gameObject); - - if (Application.isPlaying == false) - serializedObject.ApplyModifiedProperties(); - return root; - } - -} \ No newline at end of file diff --git a/NanoBrain/Editor/NanoBrain_Editor.cs.meta b/NanoBrain/Editor/NanoBrain_Editor.cs.meta deleted file mode 100644 index c91bea9..0000000 --- a/NanoBrain/Editor/NanoBrain_Editor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f05072314d39990639a2dbf99f322664 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Editor/Resources.meta b/NanoBrain/Editor/Resources.meta deleted file mode 100644 index e9c19e4..0000000 --- a/NanoBrain/Editor/Resources.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 7b61a93fc9332d2adae74fe4abe92d53 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Editor/Resources/GraphStyles.uss b/NanoBrain/Editor/Resources/GraphStyles.uss deleted file mode 100644 index 79bafe8..0000000 --- a/NanoBrain/Editor/Resources/GraphStyles.uss +++ /dev/null @@ -1,12 +0,0 @@ -#content { - background-color: #2b2b2b; -} -#inspector { - border-left-width: 1px; - border-left-color: #000; - padding: 3px; -} -#title { - -unity-font-style: bold; - color: white; - } diff --git a/NanoBrain/Editor/Resources/GraphStyles.uss.meta b/NanoBrain/Editor/Resources/GraphStyles.uss.meta deleted file mode 100644 index 2546c45..0000000 --- a/NanoBrain/Editor/Resources/GraphStyles.uss.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 28268b644fa8f3948851b25e41f5b03b -ScriptedImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 2 - userData: - assetBundleName: - assetBundleVariant: - script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} - disableValidation: 0 diff --git a/NanoBrain/IReceptor.cs b/NanoBrain/IReceptor.cs deleted file mode 100644 index b56a360..0000000 --- a/NanoBrain/IReceptor.cs +++ /dev/null @@ -1,73 +0,0 @@ -using UnityEngine; - -public interface IReceptor { - public string GetName(); - - public Nucleus[] nucleiArray { get; set; } - - public void AddReceptorElement(ClusterPrefab prefab); - public void RemoveReceptorElement(); - - public void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1); - - public void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null); -} - -public static class IReceptorHelpers { - - public static void AddReceptorElement(IReceptor receptor, ClusterPrefab prefab) { - if (receptor.nucleiArray.Length == 0) { - Debug.LogError("Empty perceptoid array, cannot add"); - } - int newLength = receptor.nucleiArray.Length + 1; - Nucleus[] newArray = new Nucleus[newLength]; - - string baseName = receptor.GetName(); - int colonPos = baseName.IndexOf(":"); - if (colonPos > 0) - baseName = baseName[..colonPos]; - - for (int i = 0; i < receptor.nucleiArray.Length; i++) - newArray[i] = receptor.nucleiArray[i]; - if (receptor.nucleiArray[0] is Nucleus nucleus) { - newArray[newLength - 1] = nucleus.Clone(prefab); - newArray[newLength - 1].name = $"{baseName}: {newLength - 1}"; - } - - foreach (Nucleus element in receptor.nucleiArray) { - if (element is IReceptor receptorElement) { - receptorElement.nucleiArray = newArray; - } - } - } - - public static void RemoveReceptorElement(IReceptor receptor) { - int newLength = receptor.nucleiArray.Length - 1; - if (newLength == 0) { - Debug.LogWarning("Perceptoid array cannot be empty"); - } - Nucleus[] newArray = new Nucleus[newLength]; - for (int i = 0; i < newLength; i++) - newArray[i] = receptor.nucleiArray[i]; - // Delete the last perception - if (receptor.nucleiArray[newLength] is Nucleus nucleus) - Neuron.Delete(nucleus); - - foreach (Nucleus element in receptor.nucleiArray) { - if (element is IReceptor receptorElement) { - receptorElement.nucleiArray = newArray; - } - } - - } - - public static void AddArrayReceiver(IReceptor receptor, Nucleus receiverToAdd, float weight = 1) { - foreach (Nucleus element in receptor.nucleiArray) { - if (element is Cluster cluster) - cluster.defaultOutput.AddReceiver(receiverToAdd, weight); - if (element is Neuron neuron) - neuron.AddReceiver(receiverToAdd, weight); - } - - } -} \ No newline at end of file diff --git a/NanoBrain/IReceptor.cs.meta b/NanoBrain/IReceptor.cs.meta deleted file mode 100644 index 446d88c..0000000 --- a/NanoBrain/IReceptor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 73f052292ad16bb53a3c07aa1694c705 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Icons.meta b/NanoBrain/Icons.meta deleted file mode 100644 index 4b8dfb3..0000000 --- a/NanoBrain/Icons.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 885c5a70637820322b07e023ce18fdd5 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Icons/NeuraalNetwerkIcoonSchets1.png b/NanoBrain/Icons/NeuraalNetwerkIcoonSchets1.png deleted file mode 100644 index 3a314eedf02c118cc7d939130ddf6ccec4766091..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60406 zcmV*HKxn^-P)FMd%zI{95zyc6R0^ zq<43BN2^w?5E&VX?%lg1A|e7F9vmL99iGh2lnmThpAJi;=Av@!>U!Q3^Muzgdj4=8-boq@bPei zn~MXS?1=CclmZ-_Q340#v5OL`6j%_gd9mzva97tw0L_sNZ@u z>Cr-t{rmSDB-dw&J{ji(8629xaMX9PiYrlR==G1}e_GeED*`{PN3~ zHESk0a6ZDLI$}ha1BP^Hg+Wn1@UhE+Q$-ORO7dY}lm(QQz|O@J<*wc+ad1O^xidB# zsK7U|nV7vH895mT5f&C^2*9XOqaXzC*|Vo1P`0tGSh2zooP`S)A~`u38B{rP$^8$5 zy$gklBJ-zIjBee!;q0@|#^}+b(Ytr=7D8}%p&IW)3jt`nwQSIpa&mGE_g%Mcok4JM z!^FfyBNi7G6&YkNC@4fxQK5BxM;CZ{c^hdwFFTV6Q)0fC4jvd7Xb^DNuwe!PhYT5V zj2JEA@%7hV8{}WJW)1v;+u))B;W)QF(ART6{L2ne45#Q_UJSBizL)Y{5&&-_iQJx2 zh66Q6y(%2Bw>%K@lb!L!PG|hKJ|4w+nFi^@!^5F*^zYvvp`oD=w}0=w_pou}Ml&Zj zFCVmv?uh8H5Ons)MXTauIG1Fj$Ta|43R_`r>^>wX#KFzY4KXn>M#7MkF?8rqYaLqt zZw z$#Bo#PoykHo_Bl1J4IpXt_&<+y#`4M@jSEQFanMkF#_YpjWgU++*t;^&p!JMk38}S z_Uze%j$M0U%DFvpq1!6>=kGSxL}?|mYY;Y|`hOUzrjF@*2YZw|x+1 z9pF%w3$j_>n~3V-jZz0MWRy7KkXIXxaqEkpQv&ew+}{wtCjt31 z^l6S=yToA1`2#S?aT&ad4+4ehR4rswtV&h*rwooxZYXm0LrSp+el6;ZH|NJ=-Nr2> z1kM;RU;u8v{dP>8I1$dy&L=YJ)BfBR0?>?GK)QEqY%Csq^ij;7JsWP$j_BR7E&g)m zU`!4#Mo24#d2@<=D9A0Fv_^ky+-3P`6@S9hHIMZac|+8|#KOTt{QE!HLouCkJeC>Va3b z_~Pf~+i>tuI?g-yT-gOM!ADEazvrHNFn8`;gtiUG9pgLW zZ>=}OJ1?oh*R1hGA)8*p37;U8hYm)%M>M?BH^ODtoVs)18gFtEex#t28afsqCq>|$ z?|wtv?mZZP)>(M!>8H`RZ{L$Jpg;C4TL?fiJilGLcH!>3@5Z!g(-0jUja$ce#q|N} z;hC2}eYctcRHGJGb5z~G5+Se&7`mUt>OV5jo>0_g5Ppnz#!K_lurhWV#*P_*fBow@ z^y}B}k36$xAA1V{XyPT8wg3M6@5j6EzKh7H_ITjDp18t$62H`ZU|bBTt?5$-U}?D^uLA%_|$yM z2v5d_Xg}bB4#0VQg?RCfEkvNEWk&_Q1&h!N&#tD?bN*6{9X%2+zWAaELaOn^mLJUu zv=D$3*a#^pDVRThJ~nLFfX$mXn*bh#?1}*R`TH}7#U1f`_u)`x4(=Ttggc^>v214= zKHLn;=@5iuSZ)DPUelRN^boXOIBrs(F&^3?;NPM?(J$`I~BUhlD#C479 zU_88}0&^EErA^>Wyzs&cCV=S3BU}DDdIeetz|kvG%inV4DN^sNufD>ZIdiaW+cw7O zXCjdP_L!K?2oDRVd(R%BL}Gu(d<0}}L_lEzI(rx4yUm4oX)O?!WrsjtPn~r zToXd;>gs~rtPD*5=5xIH!KcVAEW@OB!1JR3M(0vQsAXWM`_GIISn4?>$%D}%j%E()1a`^qZdg*8A?g%i#m+68yr`yd96IR|d;ZdLA5 zfi7LTpiL`pJn_=o_;GhJT9J@E#pqX~4%iHNRnBPnSh)fv#TblbxPQnX< zz)q#Ct~XlT^QgnWy&iaaISqS^qyOvpez@o9R}d02P?xU;Dv+MA6(2nNPdxtqPYkss z0T>MYtv3VzWF`EAs{&^D{LeNG+q-Qc1B6eFGuDk9! zJoVI5CR?)J*E<_Hw|fxho5~O0o}*cFdjR&&~^~!R)jq(7h-)%5psC$ zsJ1ZDz+ud(;R+}1CxgAbyY0PlAX*lc5-1{#%xCUW2I=J?<~GR&a)43IseG3qR4-~i za3#@W%uj=;fE{xwqU@949^4MAlgqGr?Rt}*tTg5Nrb|~2|4;qaq5>S}@k-ZMtp30L z^)DnQ@5hxx!f|~+5A=1V`96ioI0A9x;{lOiTN>~N1%yPp`fln1{PUU#@aZ=8cxN2` z-rm^_;X@|k+;ivQx7}Z3H9Z9zc&!fp4Z?}T*KPCkl+n!4S2ffN51L-j%B5|&~rUf zhj+$3fHk? zN4UDW*6|9b$6+l5;Hc-U5><-Wd*Fcw;OgmzM=$A*3!E0gErYvKyR?oNC@#1100{wi z_2r#`&TVMy^A0`gZZ-XBkKK9BNGq})5exGe-T0)c0GhjypX;zE15{5KfU^%+o63MD zBEASg9=W;y@i>fWs@BErMq&6Hpd))I7fW>%w{E4(}zCs9!hhul=y9oUSr#8_ly z7hoGDhDC|MCK8j~S->v7?i)aYK&im2e_e?HiFiQS0bJ2F7z;Q0n`f$2Viinjf!|l4 zg#i4%7N#WCym|BR&_fTw-pvQkUNZ`l@e7<%chn2{bxGy+NW^t>0>V6r3uVNP5>kaa zfKrB4ob!{frs7~mmWd09V054agTrjH4%cbaLxlW%ZDkhk&!mNO5`c|FQi=2>m8{#| z(TPN=jCBVbFnsuMoOj-NtQj~0N=3F+5SC?Lsln36X@Q0v>lz?82^HIBqHN{I$l0+O zGf7llq2V-^_xftP6)E}XP)bswpCZC_B?9?57!jC*p`C-VVEtAsUAokSVz<}=e%~1_ z1fX&)RQ_5Oa-MzmSsLuh@X}2qa9-I=IHc{gjt~OW1H`etnW`=#u!G#PxWXRJ6nkp} zjYGvjUU}selQ>jCPIgXvpt}Ns>bmi3yGeb2=iE6Ad_y|;HfG$#jf;$53hr(mQtGHt0cWC2Pf~Qtc8b|_Wk=QVs z8PAUZdQzRL4-pJ4+mE5)?eWX%3RC#>(n~KjRfcLF)9Lf8cuEU!1yr(1A*8$GcH+)+ zdg6kz8NVZc16Ig~#;aj8WO@C5jyVqQ=-jz8F2DS8Q=Usb0aRUB?$Yt&$D4Ww zetzWW4Fz9f{K&g$AD|b&?j1Z?k%K!I!gxYtRP9Fz;MP90rPlTOB)A>?>=ESUY`i&& z;#7IG7O+;}w2uRj$gRz*Dm@*;g79SjJcO2032g*!>rI1m-(X98-atYyZ~b2Cp-WK2 zx_jd8>FF6rB}dB4%s@)gUK*-?z(4Q1-+V6#VnAyW63S@a{2V45Ky&U|-gh@G_)jed zKB5F8kzXaq&t(A)1=jrKFMly*$u%SeN=a?````cGILPHpamSb9Q`#hV+;Xn&ZRt+?^S0)dMZ!iM8>8pzIWbv z2L*Y#xbgfLM7gXat7{}>n?xj(T>MEIu!gk(zTRGe`MWmZvHS0aJM~^KZyyBs`E=X+3Il#b_=!=V&46MrxvxZU`NkQCRJTokA`8NE%lL{*2Oe9hF0x3QXx|u-~t8qcQ{g@RGe7Do`I4n-0xu70GhBJy0 z=;yo?{SReLRaQoHC;)PF97U&iYg9|`=DU=hRR$(DLmw;-Vk z)7!w$E6_+?s?Q*)?`ZX_+A*94U!K+kK;3B6701KX377S7g)fL@O#wtA#-+fQp#T%e z*u-L0a^HaoLvSE_CxJLb;}#458l%_v_Kb+5C5YRrK*?+CEJRbAu4}Ko7Ps7T ziz#r@l=nXFwZjJj?S>+dl2L2E1c@Y;ia)3`6y?B??al1D=q>#Qk6bm|Hazm%)8H>z z{zU*v8Ju&`$R6-2B%5tQ&U(a>>OQuYe&A=t3FZJptmYyBM?M4VFG&XyK&p;` zLu%z|xdOXU6o!~A^;^^ zqXUWu(3;f+oV~cT4n%%tT2iWMf!|l)v?Tzl)T`j1-d&>MowI|xifpBc5bO8lZonrJ zfuU`gPte6O2!2XcAWPzJV~>^Blb=0_20$T+s_RMuI3KDB~!U;{U1DWWx@Q!JXjCC`3&`bIPPGq*Qa$TxtSx z{W4}H9?HmuA{3?Cw7~Bxa9X+n3s|Esg%W@hb;4GqWT)lzF)9DPc+L8Wh<-_DD+5mn zG9S?(s3=1P#VBHLKSg&H*(fA|a3WI3rKnKiFdEK+s5`6BhN=w*(Wd4@W1}09-i=RR zhDPNm{kD&mbrK9GXJ=DQw<{Z!o=RZn5d`^$Z?n;D&9Z6XjA z6;+p0PMc#+TLO@o$pW6tO?S&D0dSCM{nQ7A1>ez!jYE8Bsi)Vne~48$gE347Bn{+~ z2<)YSOvO3YQxce)KrbVSf&&pp;l=Vcc<|-PC(rPBFa*NX8vHGh(gn|Vb|^O;ixuAM z&(h|n47hs3DJYBtU>7Ms<)~_-0Q%h{0Lu(7NqciM1N%15C)Wcqr=GzmqV$0#<;bxMR>Xo5rEycCMu9V`x zT5#6}LSVh*M)GV;z0wO=bew0hCRQdZeiqOo8^vp4Rw>zWKC!T* z(s3=~z_05pzw*$kpfLFW5&?1VIeQo$Of`UQ2!7{^{9UyOf;o<0iv5Zt92QR1fcF)m z7n3l^iy(y0rL-WIqKLx_tL(6c!)(*0jS2f5HELAV+n)5#4BkbdoeLW!=^^N^I?z<# z)|i{>izTreaM9%PQ2KI<2jIv@d0G+x#e&Na2s>hmX8?_9WR0vVZ+4Y!eS~QW9pzh@ zt0&65J78D72VzOhhkk_uh1KRp)*((_0FcpMeeau%cXWCl+w#_L7|`=X7HGb7k4 z;ALOLaMT=D4=*$sb;CP{pjG@F%P42NUmf4I@qLKgk=_*V_gV(Pj;;8mh>Ac2Mo@*O ziD|m)L_KZ36jFN_cr8Ev?YUkDRKT5jwi=i!^Y2uG5_0|ItP-=%ilS`AG3)*Ce2*Gz z1)tqMyAXUHn}^6Y!MNs{YvJw9MQed0R^YTG01^Y8Y3wR0DnW@mS!e|qthlVz`5uyRWxt?K32m7ay911ZR3=Aej(GkgDO6RKXnD|Kgk?#9#{JM2Ed z>N7m2gp!8*&hBn*lr|h0hh9ocv>WbVCg4rlybT0#{EmJM3fgU1{-fz3sPS?(qF{Tk zdS4O>ookSZ6tJ903hHloszD(|m(6<*Qi`d72kCYv&R>#FwLnisj%Or($mocbYZqhi zzylV34l03IEzB4MqHA87{%Z%^nT@{*rPS}*#~2%OE3Ji zb{|&nNWgx|%GudjeEJm#@Nz}R04IcZ>wxIUFhsQtL=bnQU)e#pr^dprAQMGCkw|ul zMr>jhHt*bvr1Tu><0L9}PH?6G8{)#a^mH1sxPcFI?Rsu2WeBQ?s0?~@bXy%?rMx(GlONyj$kQW^}`c#pyR)7B&7 zP%7&!WHvMenAQ~VZhu}lZ3#ffjvY`$t`eJEfb$|ekr^D0-*Thz$+81j7CQ@R89DIu z@kOUlKa6bWimnlX7#tmd4$f)tDmetF!WD2Sp)#FK#HpZS?Mz1D$o*oMgFYoYaaKSD z3g~~!_lZJwtKKMZZUy(^H2CeA0j~o3@N6P`1NN4*6D7y=qV@latt_5NJLZ{D4Y*L{ zDXKHzYC3V08v-DNIn_V~M|LOEnn{pbQ|`@bc`^xFZmSr4x-1EsW7n}mvm-{19EtYr z+naOi`QM)&KmPO+tnPu!5Y)D9TZ+AQ*btYD_Wf*f`)stQ4t(K|P8i<0 z1cM?y5mCAu_Sq|l>_l{17a$t~R~4%zBo@n@USxdnPWIXGPD??^A)YI&RhXR&jykUD zdDRWFmhZT)F**XK)35@A28B{0kf3TCS1sos{+Y)sE#s-KVkTgWJ+ zuPUb=eZ0M zzVquw0SXeTIXEtoE_Ov2Mdw69E`V*JiIE8ZE`TL|X+UwBbXmUt{`*g{kY;PR zr_%pbx9q7jt|lFe8gIw|xMRl-_)yQk>93dK<#R(Y&VDIeQn#~VU4eznbwMT_!&OwC zzDifWL-qbG(vAiEtf)Lg6zX!F2F#eoZnUSLKbYdMLU|SV(;xysI@iBTB)Z0QB2HC6 ziBiA*`m0d~8ZfLzKV|OBY1kE?Xs)a1$L>@C4s(AZf0181%y6BEJKvm+P3zYnJUkr# z{O3P$&N=6_`2A~n8mE;CAm9JNg9lCB{pX*59y!!^Z=2WycMU3p-@adnv5W_(C$dVy zS6{q0)&t+}B<&zF34EziU(}JJc^9j#m0Wi$O4GQ&>k0YW(G9pXneDl#$^V2Q%p1=VpG`Ir^fNJ@z*v}(vV)1cLs^$j~y~H(`G-fb*_UvV198@pi zhaY}0M$u}{X!5Vr5h*QsFW!Fr6|A70MmE&m^m?2bY28|ZKE;8ZFw=c99!`@MD*yl> z07*naRQ_y4oNGaT+hQF3ep>VeC@Cp1ZK2fjZ|2OI2n_JUJ)?v1 z_lS6Sl@jqA!kM`tl|K9UGE4lvG6r~J7%Tdc>yHk%Ty)A>Q);gI=xs@->yA=E8z%B7 zTd%Qzu}Ij?ynuz2kmbocKbpI}zSr0B`Dt*A=#Ao5ow0DyA}C)XIXT(%^^?8CNjGXH zQ1_&i5_A7Yv{ZH7Fpu!M3H3S(KNv(>=pVz7@<~WbTBVR1Vme@6>a`ElA-;UdE zyNzlc7ulk>Tp8ASZGq#iz-d7M)M!&W;f}a?bn4KKg*P4X_wWRGRWK{2Asmy-sa$k5 z%#hA6uJrV9a$<($3YWyK+HwyLZDXb6?xOp8VUi^+bmDeY5Kp54mADjYF7s$}P@&9A zJ5!t|p{W;W&=j%lnHS;LXA(B=JA}BMJ4`^3)-EzK(sb2rt~mO0SE=;dduwhC$qMX@d2VOn@vl~5{p#f`^x=NEXF+8Kqe2Y&A-1!`J>KfMZ| z%(fR^cmeBUH{!OxjKaH@wZk3l$oVU%FgFnSh2=`5Q3YKk<=ouED%in5KTDdBL_w|7 zpZfW=-GO({1jbM**hqEZb*cr%I9StDsn2hmg9wUp|5Zj@t-2cooReMr5=13U!#}RM z5FNX4;DoJPx0-IrGiJHEz@M4`DC_UP|NSq%ojMg`y9VLf zR_o9s^+&julR-2T`775`VzxNF3R9>$jkz5EJ_2NV;t%~IfXNI*b0xRlPE}yH_H|>e zJns?i#obQiP(6V4BuWjwHY+4KZTJeKis$3e>n9;1D#}b}^XARC@4ox+#TQ@D)hO3S z)4+lK$$0Lm$MMwj|3TtG;W03RQq-e^tk7Tmy`#VnPfgp370Z?|zrq0*UwknheDFb| zs_CWRw~hb9Ujk#0 zKQR{K8^GEQf`%H{nnc zP`=AflVlf+e{F4zS*y1)5z7&0opqM!>8Pkq>j+5nKPAL|?LS48su`)e63Y(d>gsAF z7ByJWo>a!A>=h?7O7Txg0JNc|lIyEYwI0G96S`oy!+MKFo&e$rm$S(Le@$SQo>f*I zJ}=tFq<=rS7`U#hm8GYa;HusuP>0#VV))ddU~1FMD&MqEA?gBB2ENI3}o#5C7xZ2NbqFHv{mAju}wuo zCQX`z-o1Mp5nM$#Z5#M_n^@1whWLx0j#rbA-rbjmRBwL4f(!+| zQFuRZ7#{m*4i2U6hw2sn=Rg0!xN+ljia^NkYd)Q%{}f*`XZ9?-`{oe;FF_P~Jy@Ww^$(K~$_SD%WvxLX52RdZA@^S|qXf3Kz> zLBu+bI{kg*Q0ywklwmf~+zu?;&tM#i;403UPGX@>V}+&sn?8DF+nzx~;J-(bIEYVi zLo^yt3i)g!>jnNw#HSka@pxb$b?2%n2q$zeN*LP?0(=3?pMEl&(q>aofl|d$+(?6}x*W`&rGo0vpLqHt*RF7Y`qd7%|+; zO;SN~6fpTi5&(trrlqAB#Q*B6uS|nvd(l#^{eDiL~tW-^TydUKS?s!L}a;pA&Ya8<~Z_uNd{*|Ql#w< z*a~EJGlm> zQ!^pOZA(YfyCz5mQ10N4bUQz6&-27DNv@c-F4+)*o-AE<>;L_qiNw^NZc6yW5dcN- zDO>H+Pd~-?-+yl;fPkRZ7}O~Qz1Zc)xx5&~x#<|*rUD}!H^8N+;hbO3Nn-!=tAPKI z`>B$yqWu2eo64s4pSE9B(6i+;-b;z#ALPcWwWo%b6QhWnG2H01)*HlcOzyAy^PWP+ zGrqAp3E#%?Jv)-yOna{}f%g%vZ_gybp*pZI(GKemIU%LQ4H?DuD99zHtT=j6svm$n zA}VVpl}Uw7hlbLn>c(Tq^-XdQOBC&O?}~dCJLB8=u?%vu!&O&Zg@66)UkD5g)J3(5 z98vRuq)UZJzxVzJIIwRIt{fJH^V()2ymSxjvPpqeVfA>=_UP|5a@)(n1zC=*uqM+J zA8oS3kLwSRcn0E*+i&HZuQr6Qsb=|yA^`G93+cc4<{Oi-X6NjVQNsq|${ubwGl<1D z?DAOQI|~kFg>VujE+ETl=oTSY%_j2yha&$Ae2McvIEaS6ZrrHk=rxJg(wh@VcQSas zMDn&I;wF1do4cuie60JZw4Ne#oTEG-i8&6~R@N4q%fqp& z&tFwh`~sF2A2kp+_jbTA*IjVQ*+;R9 z5BfwwYwF~T-4$VYH!ct#FJui0Np==Vg>cM^0%6hUze5GuUvhDDEdO2*tWU#cT)iGF^qcu`h-vznuVaRas$DvHxV&bi^Pi*5JmdR3lFnX4D(WJcnP1?vp*ck9*-Q>IKgDUC{HSbq57hw;W6Z=g+R8~pv! zF}Sg}9Xh1{MwiQhx~^L7$7zqyrWNIsfLl8|(1;j=_kN4R>%6aQC;vy4Kv|BB4Yo(n z*w<~e>&ajwi|^0$4=bwApQ6z<%;3AWO9_j1&>x(_oz5I~vw^hPZD9K>gslYDJx*(@L^h-bS<4%7tnEaJ+>~_tL+%hMPjtF5D!uX zoxhKH3Dl{BrFL+68XdHDhlyG#9J3PPlamY|iaKqaiQSG|I1fr;VH}LH5md9-htL0g zbQOFyYf9ST?ccUx-|ij8Wv0!qMy1UO((|QiDzRw8h7IV^y&E39d=w_QuYp%0scaD? zo5lt$QE@5SA`UmVXYo^)PI%|n-Sm`vWSE{>wX23jW5Ov*02Iad?YG~WFkTS>CvK>5 zL;K+WdfQ{P?-qEM9->vM`1iH2Aw0j)epCJU-I*1bwZ{sN%_S13F217XMpExoTB*_F zkJ^{A-oL+*ZZ9IguiR@xt(IJJ=`|}t|30%if0AfDp`GQbRC3U_+h|qq$>`Zik-g3r zYUqK|fy?+ziuls=ussvoyRLiGdLMBs>iq`-hvLJHL0G!(Cv*Bm7hQzQFTcEMYVQ%J zG{GO46#0D>`uyaRPa5R!-MuTOTrv_DI4^_aUUCK*_NvaQu?Io-QLx=ETpL~ipR+sQ z%?0~RP+MwhstLxDw?&54#(-0j0H`RX{KGH4_@c>3YZn%ZD<=%XEnSKblJpC8dv365 zHj)@hT9E@Y@Eq{zc1JATSIWSca{99EX+$bV1x0{T9`yNEU{pBc%lk zH^MQ2TtO+GN6)Y7BpT~;5!I#g-MA!(rRB%A$7}QLjr~i8$Oj&Hz*IVt$K^!flp+8! zw#bM7>Z`Bf%{SjP(NjYP_Q##0LU5kzLO3SUtZv*Y$4*;5_x0(ncx|gEe%xLN)sgq{ z@k1CZ`Gy7sAkw20{`v9Px%U7&;g(_gF8c2&#l0|!qBA7|`L(C-0A4167s5X@$O=d? z)w229sSb>n!Ga{KS%{(&hqSRq{KRrA75SlomR+K8_(tQeMk)Q{zwCF%o6~<}uuP_D zOZMoakD4YWyqic!Q={DnME>i9{4c%q5>~8OL7hJY*G(LZN!~l)lDL$cq?wT4w!GYw zUO9U)*`omVL!Y~<-98+ z3Y!}Ff8}yqw`$cYlRKioG7nF0{N>DHxGd}td{dTDH|GL2^(c;hCVEa(t9)3kX_9vd z+=h3=Bl+R@;m2vlHllzi1znv8oHF$Qlnf%B>wo|IAG{en^4Fo^cyOdAx@Gg(Wq7rxnWn13kLV=iJ?P=&{z^<7Ripfk|YboVkcF!LaTONnO@59%dEXvoXCKV zEUq!72Zi`1=2{`BH%o68X*|Uc<(LPp4M~EvHO+9l*&943N%z7`(Te{Zx`z7|@a?35YNWWvI-N;|8VJP`f6+(UX z*=I3}9M;*@4VOQ4?`;Iab_{=N6yQ3!u!Vhl zzcTnl55(%kA}m|H(8LD}960cV*6EZW0LzyzHz}QS=FCBxun62bHVSuj+K)DQv0VJZ z<5ONDf2Lyq9$M>#Rn+^(ju?!7{rVZ5QAn?sElN$Cq_{92huD(GzwC>kh z75~D?6L8-se{@Qxy@^3(Li!S}rQ$kL7+NCLQR^PZj8K(&J4pJFL1%~TE$kC(-q|>){!Ciu@r9BO-}HT>>SR)Xm8U=;gx#HwxkuzuZIlT6gWrYGi3CtU@Q*e^fpZ@>M9 z_RK%O?dJF*_{+w%Hsyo`0Cqv5NJS$F@dH`p+oINHs{mhwHY9QkGuHM0Q18>Lt&T z93L_wO9+ssNGhZ{L`(gO^0D$8RK-}GjP(5p0o4$XeG73-P#5eS5QdNDZNe+Gmnk?Z zGBUD3mp-WyfKow){7MW_D&$=k48Zk)vGC4I;11z@v_Y!_{pQ709Ww>5ugJ$fCRGd@ zI@pwk64yU2kO0U>n9U9|LG62B{q7V~D^SPlk}9k1%tI8pXXnf>bbs56A5S$O8eP(6VaKin%;x&aWArIs({=-k z60NFEJIVEx^d)IPYnjS-eO4ZbzK_e|ag-{T(=M}!YT1@UmJLSgnNpqU?Q9rmFVc{!AIsO%CC;u#N-I{qf%?KplK>RmrvM) zTAHJME1H}b-7(IHlL4LaXw4&PPO$j%U^S`H*@n@cS z#_T^g7blGF7KYn<6vA!yY`n8R-UL#uBNFc?QnNF;;qb~>3}D!@8xh!vY~Ps-#ngS^ zyWH{>d@#k_f&~-{82o6mKt&J~9=(jjVD$l78oSX$6IvMssoB;Sv^q>j?dO|`3%Z43 z%U&n^$S7GEOot8~YBQSF`Kl*{0LbF6WS);d{@6V3nx_)k9hM8z>Qm8M?B^BG|F zxgz{jEJixNa`s2{Y=?8ga+qJT7Sp$v81IlgOG0Wr`%n+6Q6!c6YYU6=*rm$ScRR=|3Zd(NWICKuHv;uu>OL6xF_c`%GsbeV#aRqA@;Arw@AZ z%IE=1+=^6mlVoBWnD2`bJ778{IloR>4x%9xRyCU(y z7hj8AqEDYbrp_Si2{Z@tZ(~aG^UIbkbC=jJgR`rv3kLP-j3}=>{J16=3*t)+*Oo3H zETrc%9Y8|gJH!cH+V~@ky0&-BDA+{|peR5L_M%J0g~~nWW&s~SUNTw!CRztqAa~QR zNZP&`b9ZFnhh3#c3Xml5%ql)AuJ<{-gu70y@4D+*ac@RCAH)QTs4L=p4NfUsdi?nD zMyjnGP_1vxY6B4RE6_)Q77G?EK<~bN@!F6)v;h}Q@w;^ecVGN(V=f+iXBtuu&|451 zYVyu)x#zWAM!D%K*QD%15e>zApLgDQIBV=EjB8tjetC<~(Wl%L?vNTFV~)aXRd-Lw zFFY$Ehmg)EJ@3a|R%f0ABo>U_Foqxs8HbHS4Wb5m$W>mp)lr3^m)EjNPlDBaM4pSW z?W}v%Wg|(9RiKbOA5wc{80I;QcSZXBjFB z`K9y6#KbfQ@`Idz{o=)o`^&>HckWy!-nn2n`yqC0<$wiiwqj92i4osLz}0^2(oRhN zqRMoBS8wzlJQN}0{|?s?x4^E)U%2r6!GnwD=*mUohBypdKxc9Ve`0pjNI19ahk!tT zbahKZd;2tINtR;|RRXC6suj^uDkk@qmc8DG^D6%&3Bbdr6>d8-2JPMEvwRM*rmm}?;}~y#kcgkfdVem?&L(P%8_ARd6OlO{ zNdMRGyLaz4i5~hpA|isXa7%aMso|a_2=7>dv)fx&QF^;7{ABQzLWCE>Bv9|(nZYVK z>D0mbw<_eUq<*KDILD~AR=$A{OKrPYTFrjTAqAk>yjWg*oQ z6!8(Uk)1-HCq$BqaGWM!iNDtcBn9*(SG%bPgM}7ZgiDfu>>jMMX;OTd@oL0h5AJUf z3j=k${t;u)94Vb0@av6NcecZa-~4Fm`NdS0^kO8Nj;QOCKjPB$`Ge)+KH#3aPS*aV zOP9jQ)g9wT4nfz}ZkRo90XFRCgbAuhBs>V`G>Y*BpP%-MqE3~Y($Sw+t!_WmbTb{#y&UeCiUD-{EMy9` z@JfuVK8aJ!N&s>g%_n!Cs=`b<=WLAgKLGobr221xl3Ya6-{Q}{*6BF~CSgOITCGXK}-D3>2EQ5)zsew@oCoh>4! z03o@j_Hr&u6It+duVvX5QHkQ%rQR^>8X=T`4v)3Ao9K6KP*Rk=KK=0g_Hev6btcM- z3!v0qg?bMeGDJscI86$aIG_6R%P${#@4fd-$v77`cT67M6aCth;>U%{uwh@edBzHu zQuy~-;S|gDe0p3!ed#tJav1l*37`pc`Vl?WNDSQ>m~pS;&%AgkKI8tF%e9tKwjHGa z5%h+DYko3DQ3C!cJOHuVb{YaVV88&lxM&i!!wJl+_KR0NLdYk*TlxI1p1v5viaj9( z+qj#B0rdo)GdwUCXGA#Rn~mkz$u7U*?23v~eLA^AY(zlFFQcPUiB&&P63@Ct z^+$)8?g$PFgnhzt=Fq-GxqiL1EwweZMFqBZ8yDnnq&Sh){j_it@6Ih~Tw^T=RDWA8fX+A;O{%-|9e? zXa)213*oN~K_OnZl`Al%Kcy8SxUAF)8J1e1km0KEExDDBQH+GiFF4Mas-9y*{*>Th z`1j@@y#L)VD9X)3FP6GeQ&w92>vEMNF4>SjNbGN8Ux?Q}`sgF`?ERQZe$nXO810{m z|IJ^6m2t&p{RVO^9~=Oj9Z6jGCuJRc6Dj=-r271!F+k6#HASvLHzVrcHr!IM9r0ZA z+3^;^ReecGiih`EJ??2=T7n4oL+IpNft6Hk)yYW(aurTp7id-jp!(Q(}=S<)|!}Bm#vR%e}aZ zudV!h<$n5>N%zEE6_zWng470a_>VT*vD=U?EU-f;DSLoxDN3?Z5SLC>pzNKP-$BRkK=EPgRa7iO7Xe#1W>qcej zc5dWoyuZ60KA!eHi+W_3U=@Yho^i$*wvpENsn=;vwu5NtzlB9S-cV?HR(2M`!oqON z1tT%uKM7yVpkXeq#1L4iY03(n5Xr~hrYl8&CrH&Na#8EXL2dV|^XerRe-;{bVu4m=(n`8LHuoRw2^wX4c#|vshe(R_s{}&;sBnGN=rfQSfQ9}sAqITceWmD=Lm-g za_EUKkot`#?bTv70XS21iyQ(c-w<^2i8nC?sywBhj#_W?4C{ScI}Xgq$8#fZu!XvRUcD->2itFoT z1KmO;s=IcHL7(=mNjS+LD!oK$p}$a^ZDdxX}XmVs|p*>`(zC zg_26Q&wv+R*x-eO2?tF;)I$$GU~12)qOQ~z16?EVYW@z~9lHvP4p`BD64hl7P$Z!G z0`{Raa9pIcu5$Q)arfVnz%X13`7h|e&qHaWAUCvKLvBGy2)EGApiTPp3idxB5m-lB zIXaTZ9fx)o1$M{hx&83uJ6~Xb;$Fjvl)8HU`RA8aB7c+aIU2&AWZ}>kWe{_wD`xnB zUijPa5LWWpgk3x0@M$a!waZGH-q^XWhaBEM>H{od_|0qtZiGg9fXx#w;?g=b7 zQ(;st-z6Z1c0d^FF#Ai8TVrWtDOmf5->R_lX72GJoUsruJK9$?M-o6%d24!IKe| zw-McgtZuo_5Fr&1qzu5X$jN0%HnHYa?)Ccxe*Yu`;@X2&a}%{U6B0Z*#4=DGgVa2h z(maMyt!cfmOf_X{ycwy!bNZgxXi0<8Y-z_(58Ht%hutt`vj~oS& z{$G?d9#)SC4>wng=+O?hpV0;*-FBcjX}9s7EGGfgGf;Wlt zzKmcKJtt+NXtU~RoB+_Vhv>3h@UFFWhwnshstut@`jYr)UE%Q0nPHgZ$5 z;VE6K3YRtqH1~g+H(z;0&iE<86?+e*p?9BNxap>wO+G`l!|8nm+3)|DB((__ceD(Q zDgY{ko)vGk1+R+~plr1TiPozh4VVuMtVI6ftqHHMx`+ynQ4o^E)#{+k-U-X{LhB0kf#C8K1Q$~=J{m}1+SA~s z&AW=!|EzWmCbUOi`NaO_xKgO|uZcgzS`$S$>&!Eaq3XC7^^atx4@K4en-S<%VcLz$ zyP)he#Useu(3=Qd8$C@U0x#TWxt-LpxNj)$sFP~gfwDO$z)5z z@TmX?8P1%VPHzZ(#p-ugv+tHz*I^tLMMo29C`-K(l>{PgGAP!R2&v<=$D}sI(oHD? zSbrkF)RCHyUy7+B92E>DU$vT>nhWvGyi?CU7&;pN*&Bn|3)qE;!&GvQ%6ZWco6pu^ zO^W=?D7}V}rEgFB@kiJ(GH()V_TSpG!a{!L8ZhY4j9=x_WOq>{A;!Vm2ns9 z$utLW*Q@%k+*cd8@fCtuE1PVNaq%8P+){c%9GTl7x24vnMrcEe9Gju&C?zUsGuuE# zK*gbJo15H?3NBK`V>K-?$JfGrc2L4lwPdLRF|;)pf}wL*&?7zkOuR^U-+N#E$ejKH zQ>;=(KL%wLk)SoH&d>I6euL88 zh-eNS+!vnX?&O3hGMyx__YQ%*heaE;lnn|5;)&T_5%_6Sx^Z>NZ5$O9Rj(N}D*=!c zP{a_pQa=uo8@aha!C0NRam4|)b2FO_Tnnx>$Ji8iu2j^L!aU=?)cVv2+%)i_^-2$( zbCEu40~ZnlLqBS{ggKtaDBICwFxGTDubE28LTRibR*Dl)@{n0S?wkVONPHL{i2wd? z1`6owkB*Kum7SQCmP0*MS8g&{i|*u0UwiGf513JEUre{ku>QU9k0IU|ShfcC1zFaz z@EI480topfAqjef`=a{=cfc;RxAnzI`rnDj@8V9g2*;>}vN?R}JRF_GYb-48h94Nz zUZ$PsV0J*eO>7}6D5m_#@ggR0NZ7$$qY4fV}v|_Le#THHh0uaI_i1cTO zGRu{3+qTt2EY`RrlAMb3O{B8?@>@jADnVtxxd&CUW_8cLZ=^ZhO-y#CFv$Vg2x{rc{^?>?}-bs}A^a``oxY|Xfii{EVfABB_b|1y(-)vi7OLq)7yLm7K z-C}~r*j0`>y8172yrUTqX2*g!NhYLS6_qOcCRTq!HQzSNDoC{TqGeX%NmgDVix^}w zpNHG8S?1;aV)M>D$jIfcYOcGJ6&P+yL`mhQmnHn91INVY zh9RxX%-B{gUx76%SD=is-BlyG^tpsF3;F5E*gE}fJiVycv>sRIVSy0(FCDCGJ|jz7 zUmy2>alwux7D9~q`H$wu?&F-Woqiz4P^M=36Iz}!P}_t_(M z`yONQzxCEzb1uE~(#bSHUC*mE8RzGL{usA5K{@2Pb^Ub?MZfQrtuvnylk^encnu6!zWzf8lA;;QShEWAzW;(5HCkVg@L zYC8J29^(G?n3)oU`&T<*>-MeiU^Sc@ZnzCUWJz{PN2kl~3YI z@%j9*>bm{7p2A52bzTd?BBm1@4x>(6r0_uRR9LYgu>IE6kEye%?Olg zP123>`V9yps@ph~KR5XsH)tSLlPk%TgFy5||Y zn{7NoN_hZOxZyfrls_km6>cd(Oxp$AzsSl<(08h6`_gLW2<)`(qAf_yL$F8`Y#%poaRaX_>e*5ibM@B|AIr6i3%g?OjGf{y)PV|~g8aD#} z*Q*4<=_{>hDBI#xc^5%5`gP@Z_Kd}MP-x>R0J}D?#g4cnQ;A90oeZU@H#iojp{w(L zs@9Q)04M^IY1Z`$1gawZ)%*T+IzhhwufP5pZ@>LExj)NqjTwyd8GYicoRC^LR-JB8 zkAq|YQxc34K~4z}(w12gz(45WQ-RJxewUSBjAUgnvTlM3msVXS1yD-oSN#4HLt%Ox zn)Di7s}3MLK@3#@Wxy#7RYgahBl=!Do9V#|thxqTCmBq~M_3tv!`s>8*YsdaS>{3H zk2Ch1i!Z)7mwNvs+I1Gy;kGAq$d)Zzex|!`qFTh4k)4koIuQRH?v5_GOG(jX(5W6Y z4+<@Z1^z;I-YvF;fLDJ?vwWMMeTX)nh1f@9lnV-`qH*6XAKb5a(}_#WTP zT!>tXCq5jXo_~XY&px1HKZb|+;4prK_j2OK)G19bbVjy<|-u8ja*v^KVkeV?rezXiHw2!)gp0B6+2a95fK~d z8S{wz4=i@V<{i7?!V0=qTye#6_HUGxe=1*13N-Znj|D_7`QnQ&kWXvnAQpVN_pC^a zwp$3hZ0bz40HgMgS5M6HU7%_52G7@WVfcd17GU ze43rP)0z|cxl-czs)fIbhA&0J&5pOC;Z){ZDX`*ps?=kiNIkhJ&uSD<2ekJ#S^MDM zqo{k6(L`-X0qBL>U05ci1*v|dO2#t_rR*^zi^l@nHNjpO}Faq-1h zEv5f`$Pvdi(I2cGF`YI1F8TcP&rO-PL4yY1zVmwGY%GG)!Q?vKRRlosAF8{ju0973 z9zq;d7*&XB;vmIi#wS&H-P(0{<)!Dab=@jskls%Zg`z7JsoGGmV~$OzeF8?b4a1U< zAQFJZroZ8J*IieqoPZ`F0J8WqNW-*SvbT4@l$)-=xei~!UTz9KmF5E8OICkX(S0YD zK_R@oKt(wccUkT&74PUtBJkK?miB7TABhyr4%`I4?6&Is%NVDGC2`}!AfnV*NPqaE zl^=9ny{bSlk$-J&TRgtf4GUvgZmp~smtTH)0;BFkCYu~+@SDS&{zd=NNWjYVC&#VTfiR1K2&C;%3ao+1VCP}eBSfdnLpy->YrJ3v>c1+ zZAqplT+)eZN1h0{m*!zat8}c6cEyK_;_=yMpP7>DQkQCzbQ+xiDC_UL@4mxFAALkh zTY{T!x*j(MZH8ka#r|dn?dz|%)3UwB(ytZBGNP@e=gJtSyns)(TBW^TSk0_ZK8Pqi zjH3{uNrz1i%!D;lL{tXKtLPChxPLMT5hq&4bk*3xHh6M*- zCazY~0^4&-7l)#6iK!vKrv8LhhP*19U(NtJe!!d`P ze+TPFs%(}&-K@<8b|e5U#hDo8nShZ4icJj0Pw`w@j;}RUE!ka81RVH`BAqjFQIGbF zbd6xq+f`t&6gqY4RO>t&l>n$BuUamt0&D8QgBU+<4DM{b0p7`U)!3Y|)y!0>rg;?8 zOSm99Ok$~rO*g9M_&R;XYpmT5yh@)ojp}%C5O7-``drCvZQND&jUo5HX%1C{Buhn5 zyR2pfK7nO>Sdo|aA4KDuYSh)0!!`Ce%ZXX*3R>gQEuomRbTvbZ?QqVy=Psfc9^4$r zPj~UFME={SPW{&8$@l9NhQAH+!bJDAa7|30+nC~9U6H?XakQ6|0HnTriV09vDygUS zO`18OwPpyn%dVLim@$*lNWhyLt>P-WrffU11Dwz^CxLNikAR^*2XRi<5NwZ6FexcgAhK%F&@{gOyBP%_xNIYILzS)rv}!h@M+yRnS$( z9Yvn$;Ym-b*K_AXSK*5f$iP%KOPzmj?pY;RhW8c7_u7{}blpSE9o6ocVfn^&bV3ir zx_s^b^ZwF`X6mD?eL8jO z)T0aD_GQ6HJ(7WJipN9$Eeeu)g z5NzDO+my>!rR$Tme3-bLK(O!tgm+Sxn z<;tr%OS1vzq}+r#@e~C;tt!9uqjV=YQX8RhcEy^!VBEPb6u&KDLJb3u7*O&e{qD_z z{G3JZJ@?$xm99RQ5@P?^V}|11(ZM*|e-A}xB0mX2L)S&po+3a8gs_n(C7LXDhE`Xq zgOpI41@h8}{7;j^#L=S5SZ4mIx{4xlQcYx^ut}sRG#))m7TJmNEJCMHQapl+@TwSZ zP0(l(fGY2@?;2LET7`)AoiMqhBfPRUSFf{?^6lb6C<<*lxnGY!2DXs!yyb0u||$ifJ~R4LQs& zP4FM*SxPBI^OXPn7D_0QUc67`SeoFDM_$r4zqY+GKO-1-E#8fFo7a&mI+F9B`3YOL zPdW00Ci^RA%9JVb^e%KSDk;TTV@BZ4k-->Jya*1t#FP_V56)9M_K?tW1kr+#lv80O zfY{hrQ#|8v;nk*?ow3Cjp0*?c*lcABLFJLtxBxr^zjYu5P)uu@tiT`dv5e2z=76SBB-+2RMJXu=Fbkd z#C^r<8{k8q)R7T#J6V#N($m{KR@rL`Nmc#*8ZGs_pPFjS%%AmE)cr&--#sln-D!(o{FjrWlOo8PON>-6r7PZ{`#EcTrTd$K=W9 z&Z8K9byM8(=xb7HoJ-`7U|d0W^=lk8WB~3O6N;gw%ixejOlhR&;7p0ITYy!|ASs7L zfIT99nm!GKhmC}vH^qadf=%C1w|$vJj0L+nhSe1P#qgw_s!`17aLYE)Xt$>g&ZQ`m z$z^`5#^jFljPzO)ggC4d2Z{f(t}0I3-`59Y+q)s4h!|)>?vA=XH{LoL=M-<=mTUj$ zAQAyCgegwJg@Ing$}keG8_5-48pVVe8cM$2K{bKIz!br#VbmhJ(x=soLl0eHug4G} zer5Y^6~|(84=Q7j46x}zgr}U6;a;o0zlw883TifB+-XH~p+9%bo#2u%TrYpB~j$)L9s(+OCCNMCGYaS{qO68Xn*&x`0QRJqZB zYvxD-;A2-{+Meey^H$N5H9@1?UV#;}X3a8HZMsGTptt)0I25HHdtXjmHRh)g`6ZrT z(S>d~E`njhRUf!MlL#p8{p@xm5`(P>IhDUw?>d9L{H;3Bsm!P~-ULNhF%02Y(7Ajq z+Q;(Ps~I82HBv#1-a&jIA^=s#a_rfS@{PiMMF13;DA!~gNjA-JUj>~P`E|lVkF)W} z7mKlM$zoHcjfp)gKmGL6QH?UWS}sDpG?CW+QRMvY^7D`C-5!q&B0@NBusDAW9-~?= zecYeb_;d`{d0>d8X6`+ZhBse+5v!k=jPlrTk9%H&ekkRJTK73cjV;_zqyc1VKgLh^Bxd+Yk#kU8Ie`)ykVisey7?9;I| z{?VJg9_b^ulTm~S(j>sORVejUooP!Fu_Y|plaE(sZo$qaKd~_sH&Gv?2mH_wYven>fR0G%U( zacy@8oaskVi2hfFK38S_BIbHE3UKnWwTz3jx;noVO9suo@N;|#0={30+lFmIyDq)! zRZ;htanTir6mtFL@6nQ-WRPDi+}`HBRF_cRrZcz?|3YRXLeS{XoNHyrN<$S6&Qx=_ zn7V~@;u?TMI&oONdbNqKjtpyO>D=i_$8dk$MS(tQsjUjO(WJt4j)CUH6fq?VzMIG| zBsPeu&2jYn__s=4-QL?Ow4nIpcUK=+r2K;6FuO0>B!+6oei^y3%Snwj64>OVaf zB_qw+VsWL*9q8%C79_Bw98>ps-CpvDo4C70ilH|T$x!>v?qIxxwIJ#U{y*ksF6 z^HtM=O($|ZjSBzI-B-ZJUEcq{FP=2sNRu>LcQ@)nOK}+t*#-<43>kxsv9YlgAAge5u;LCa#p>?fCT){O+eqV{-2eT4zPWNuBNw@{-~V~_dY8NJ_xV2daU(p~ne(8Y zF~)Ti%Y&J6O6kJdYF$PEQUJ0r1F?4_Vxz**FOoZ!&)sT4xR9%>a?A@51<=~$jx+&} z?5C=}+vei2e`UD2WhAS=P{Aho%TYi>c6T)eh+N6dxMc*Dkzxrua=?Um)b#H7tiQFP zdg(j9OWVuZALdXi2uajU9*>JCmdFQUAQ9S>cq_SwTSX7<#gT?c{9$*zaqsmc?>TAG zBxUfugSq6~4*XQQ)i7gCAfE>Xhxsuf=0&3-abv%uw7phfE7w9AawYE+RhIN9YAMCt z8L?LQvx0g~tS>)yr77>iFS+me&!~*xSHopVy{Ww%Jb0ejqH-0c?N45g#gHjvra&J z&Q4uM0IK=Nm~A?buq=^(J%Sp@UkeCYXo4|ec>ONhtXbk#u03%(fl1zhUmUiaW)#7v zLLUwwJ8@m}5A0X6{TZaNdg+-v3H;K;9_PkiNI6Wp1+y8h0KXHUi>s37pep<-j$Q{= zVv0(039-lmu_DXzLX?=HgQf(Y5`pxR2qPC&9R|4~yR~uL>4?Q2>EZX;+MQ&n?JUuK zCP?|;898#~Gj4r0J16irFn_=EeSEPK6%{kW3 z4q5vyfK{wUcQqwZRZjvzmDcB8s-4aPbq^wzm|YaXz1O+&Uj>132y*hFLiug2ei}1NikIninb?eQ>PW zlTojY2xmcDrEP1gbr}K3rpgV5pN}`9dr9D$^gUB0## zczl!P%gyZ1RwZ0v4+37hjR0pF`x_QTivl-8&OSYaMD&~Eck8N?|8E+DzLa<;k3=0* zRmok?m(p^}geb8>V#k;mPr97iiD=0F5f6&$x7HM-qLO7{d~Xe5wsx<}-*b&KJ$mDj zJ-zY4nmu$ps>0N%Q$_g?(iN6R(!GsreLMA`$ zz4RA~?GW;k+#gQyqs{YXdhO(YI;(u;(u;2U!J~%j7&vI48FyPfb{PRk`KK6wKw1D& zqC+TD5h^s87FyF74FHM~w%VGILtZ)X;tmE+Y_k+*QW^@ADv?Epv8XnkFXQ&BfwTdf zW?V!tI)o~WA-#iuUQ}Hbbrnur$wT{nvl-{6?~XL$yCc@ON?Ms58IBOoYIs)`7$+m8 zq8E1{ZLki#l5*z+-#_zdZ+<`i z?*Bx>FW~o(%)fBqLe!EEo=J1xt)pvkI5QVdtg)L~eJa#BH_=k^N?cP(a($;QlDPQN zPM!h2-aTNM@B~PPJJ)`8rRjb?WJn}EQpa05spojs4ji`|N1=~C`p7u8B_}7FVjIe` z6BQL@l!QV8itm>@@t2Fgz=rkf;pfq4bW0L*)uiN`oj_02p_Z9f(TLr;zvd~;{`2mE z%*rsP-Oo0x|CrIEP432KSDo}{mk~f}YO3)!j*ab!VG^z~TZb%RL}fq~V2PSaCN_=` zO^arBo!7`zxHO&4Lfmx8P|}*Kvdh%Pj%o^4YHw`j3GMtxOj8z^h}Vw}!)q(Iprok49O4Hbd{A}!?&sZa zw$nqGekO%%VC=o;Bab|SMT-_Oh_4hGx4B7J zN@BTkA0ZO{FqN+~mi3Awk`Mvtj&*7}3qm?KkxDB=^J{xNu;Qpc@~FHiC1FzrV(GZ( z8f`cLi)PP+{E?@#N6Puqg_S-3ILMBK{$orBwvy?QFc zqLhRC32cvUu;ena*4iV5@XB%GGGbFNeW7Q#sqvB6mk&Hf_Vg1X0C^tjk*Xx)!!lk; ziHR6HcC5)wCqpiTEpfl z5vQyZDpK8zRq#CJEgdapnnu=Qs>hbpdl1;v-i zE~N$Q5(3acKgO!0l8K^2dcd!~u+1s@5;07OQZ z;i4(`og+X;RecqzYg~rgd`jnkBrIZG7Vyw|25n?m{ixKkYOYH&JZ;)EQ~Xj@Sd>sg zP8LddDznzCS+j7%4L2}%|0XD;HIH`g9O_3t&CNx8athA4`yp0uWst`DS1}^G27jAo zDKlT$ZLzxIHsljuR2s8|vWG$oPI+E%iQj zBM!=$?6QHtlQW$9SAT8H%jwE4f0St3Sng;|6V$ZwR4Sm6K=$pz>~j?t@)u;sHgi#BFa4BM z@^u1<+-8+DV`dUNcSZTv${1SvTIXs7pRs^QNuG)xJf*#R$1)Gzk8$xOKQd*PRD0dI ziOMVg0c+vm?z``%c6|=!&!4Y2dMJhUu3fv}Lp$vBGbX}2Jc&Hv-IODaQ?30TQ-C)b z_I4c+p3Gm%_-th;lA_Xa7@eQx%IA77e=PBvkH_9vi39ug7@1XOExP#%_#fu$9#{J5 z%rydji9S-IDFwWg!$bS@#ucN2a6zw36dx-#9&zHRs2cu%{!rIw%C zrEjZjUidD%5pR6E1r2r85b(Q_4ona25&{tLtJ;s3mp9^t04m7KnjLCmK3rfW`gmfx z#R5yd6V;QpRKXEaBd8DgZ4-77!q?(-;BblM%p)1lSP)z|IBksLzvXDD~3ggdN7YO85cd5x;s4CO&M(H!^bcLKL@65{(f z%2T2i@9wv>@Y2W+)P3zC07rj%Kx?1%8(Vr2a6e&kRhIR4Lazus@W2DO@@Ln!sYH<& z5E2q%H~=r@^>%71n5oyvjRQ%Xz(DYy6RX{4^3{!rrlRqXrRbEEL4M-Rm0>6M(ZXLm zb^Gdi;r5jtCO14i`*7if7s{!ng66Ahc*naf+|_MgiSmmWux20V>I-iYgmZ`W#FdE! zh_Vy%2^jLYQ_z*liz_wTXwJJ$;BOk8_FK9bS&e2VS}`PYZ+D!JZkt3~9qCN!)x2># z6nl$L--*v2!o{iF7Wy=pxYeelR{h z{&rsane*lD_^ezIqWnrkDcktmSu=6NgmNTP>1Pbm9HS_|wn%!NN#x>Glc7C8DI`$I z2S7d-LL73k`sWslA}e@H^%4wVwsfA{G0_(Kl>SO{l8)+FMH2#O;ks`BigUIHMc~VV zQTSr#4n)$mTEMTpZ_jzjc`RK*05UO%`ili@5cA_7O6VfL)&ej(Kh%77b<^0FHorf+ zmJNOt55wGK%hlH$AU%tHIeh$;@IlTx+c}I3BBbL4EFl0@-I2{*?!^3OD(&>3_~A6^Z!k?r40maUX2V4m)e+JpBImzu%RdoUC5m4*U{>6xsLhfB)N5 z-I+3V8tyu82u4>f=7flCoAx=3^OW^R5tSHO3Wrmdr zRosC3xpBA^!FeL5o~vidX$iaZkL9t0j${t<gPAGiV!byvB}n|?5oN-pTk;yV!x#nY_s_h^GFof{swC#mKcSTu2*U@hq9~9`}HI7 zNVaDJp57macUSK-jy^I^(#3bhz<~p&wR?XzTp$rp8K9qe<{6UwV=fM1W2Zf8;qt03>Qst>F3kUa6E2M{7~z>8#BRVk9l_taL4OZ&-C+AaqXv2< zu#Xj)R=W2+JZcvcm&!Rvx%FfrQeUUDN%B)}KPjno4yW;486DMW7=&kaF=Bkn$=Nc; z139nul7F-`q%I)G1VUjKfve-h|+u zy*e}l$^P@^%`+xJeHI7Me&aC>A_nUb343G7_lKY3=f8GaBe{rtuQ)uP)faEB+>J8o zUWN`GsxoX}({*)DyB&1H1=9Vz^2#gt$3OmIJmUKGAAozVnuYnE8{m;iGA-=tfJRO# zjsSlR0eMd$ox2Du53VOwA4aPO_el<7;(R0qiGy3h-#({AE9h9?ENI}RJ z{%afELw6AgR9SMpWU%x2o2a_P1$lHaH32-d3670$yf;1eI1ic}aU>oHXwN)rK8BB- z;QFAPt`y7PCfo0H?TL>)$SVeLpp9v=I+-TVNFlMdnw`L(>(v`C7mmcgzuAK0Mfn&! zco67yw~W?@(*pc*c9Og=rJv&Zlj5TAt8+%+T>owG%3gOe@M|)fA2;y~BIcV%S|!N8 z%&|de2 z%C#p)BR3pH&E}*guA?ux8*!-zhyax7$>lKjlX~jJ9}Sg=Y~cFG$6(*VbmO(Cx`3`) z0QAHzApmtCbC*guWpdY+XEow{@jueOb#qf7#WmdA-DLZuER!g$3_fBjr5zp>O${+O z!R)#o&jZzJ;}F;9S^oXHCMqHlv(B7HB_7Gl>1vFMgSWq_bs^SYP0T}^j!xv43j_P$ zor=kLe#v*p&&xIlm5k8us@#2*ghuY&SwvuiNrcQAxcS+l9MOR`j3V2! zWoLy6wU%4@XvP4z+PaJY7+7K)PI5AjU;_p4vq#0kD=*XXk#uzwE?lS3UilCFkwp4a zZnP?@%;)CIF6_@kW11^?hy6(Am&c)ka@2ZLlo>o^sP)I``rng-_V7UoshvadCjegs_M_(Z)Gs9RRe*NoT?`BpXu8LcmP?U#!8ov7KD=4FnDnP|B*y(5E zBk_xrEQHkZk5 zo32Q{3;J4C00GO1grBYee$zlCno5bJya0~o>*woIB~{1u-Sf^=(Nt$ELy{xNNO=zA zC3i~WMps7-!hh>#;el6{AT#}-LAtl!etX)DH{LkplS!j&<#BpW>_$>jybDu&4d?~n8~ zn9?sC3%|>yAI7&h=bUp)AdvHQt$pq?o{|%YeBkBnwrzg~KG;`>>K-K3?r2<4VO*Jg z%wZAWcL4aMVO~cz?SVBGt7sw--7!vA`fRL2ei`G>iHzhxqNbEll7~-XTRG>d$U%6c zdMfUF)dK$By?f(Vzxve{x*roAy1i}V6TH`~StBK*sXy=Q7l897_QS7+7PB-pPkaaV zz>HRaLEzS2%F9l377{Q3@7U9h0FSQaeNq%&Mo!?7DbxeRQ<0cv)rQO=elptME}wSx z(?ZiZ633$blRLM9+)moV7Vhb;uWX!mv_}yp$2K5|uIqbfj#g(VLo1e~RuPni8 z`Zur1wRC10i$og3_BsuuJhU$smW;-OZ+wd*hb_u~(@i&RW>SyQPM5js;|mOUnxp_H z0l(OPWt}~9+(6tlrULyvv#Iat!lNB~sdv4)+K5ynLdgpBz!_GuSEYmdCsEigiW@2t zYq(OKdcA~n^y*?0@>2Al6ws9fNa+F0bh@GM_J?MNUtJ}}_V&e;5q)s<=ut>1FaHcz zT9?7pVGfc^D1Y}5mbVG=CHCP${I4`}nOMiOx`32lCPN(%z5cVyMxOz@iR+eT`*gO# z>=B6bTT6QgAKfkdMSD|d<9F_$ zl+yC?zk7vcD$)T^H6mYG!o_r(m(@vr7NP(2ET7sv8FmH;ccUEG&& zfLM7Ila+^P1QCQhG^Lzmw5|0!uQ8%Z3-cDTP(EC_uFF$V<2YT-#yI)F>H?ZbW01mp z6#{-Cf%Yl`YkZtq=bALaEbstUcrm~~X~ZD>@!Vw0i#P<^A+G-a0Pt%g6xXk;J1WT} z%fW(4^wvs}H=#FTL%fhySZ*9yl-f+;yzXvHmV?yY+wj7okCDv`C^{#iOyU4!>9F(U z3_!%z`s_$~fF{Mh{G? zUjv(}T>dXMu3t$teMR<$rBsxU zzpN1^7Xd#Dap-RqauJYwJKbyg;NS8(m8YpFfAgOm&vDng50wFfSl((_iZ62-?qy_P zs?h_S8jyDK2s(`b`$JPd43Hy{IfKR?N{|vWoL_F=*gGOIrC^6U=WYo}YY-=mK{iX~ru311ePm^N_? zZkrKHy?61$3yDL5;ffGtIz{7w{-E{7zUE$RGuVm*`}K%#~A z)Nl&y>vT1ny&fUnXYHmRQ%7mkY7g7!dg+<~wm(Rwes#h#63pe5i zZG67iFS&=_zmC|R=T(U(1pMdrX&M<1N}xvrqG*)1F{_K6fWKwS78BAc4ZFKqT3Q+& zdGH~6(Y6VBndJb8GEy?Wx+tbzFVnhX>j%{KO2D*KjRdF7RJ-8H7>{W8y; zZNY*Ccx~ZA%Eq2JYy1G*G&360Y@6V5{D`HgZ+=sE{>?Qpts}t0d&K6;AOEq9R^9;x z8~uJfJp+eYa=sRP0MCIEddL*4;IJYBx^cqd-;T#BLR2Z=g&dRzQl5RvMIhI6eJAch zRjo%50Vt$d6|VFwDV1fNk}|LtMF@jA*w<-m4a`(jdIY^}k?`~NF=ttdK)LF-)p+bW z-UKX4dnE<+cx7>0|hr%gcow8)IKPx@)S!3lI8%3mD<{OO{bBExm zuTznqn`wZb9)91PPT-eLM|S>a7~SVd>@^dOD)rq@uT~nV@ zoSDozyyp}Xt@f^!rr@uy(wSstvsGY3Zn<}iVRoq@mhXtOLRtzx9ePpg5CZFo*3!I) zRFwT$9D)#qkb%Spi53d{`SLKM05i!QupdRM4kwo>&LE0hfa9E<=-=HRxOiT573k>| zia;vj3k!=(fLeQx?Jxom(0}m32UIfbM4y2}acxoo0*ZE>{t34*Go2A_h-7~GhRd<$ zu5kqZzWhNyHakQ!hsI&Tp+J02m)dd`s(kLb=a8J7j0-NfpoQK$_m#wo<;$1j@y8#> z(xogJ#f_5esal5%&akLRWk3uT7L3GGOZOPyA248ml6WpA@XznuQ#*c_#>rCMMxm>? zhO?Q!|BewAnCP1Y&tgJ>tU4_LNyi^`(jD3oQ+A)u0Wy`=~{ zDR|{brJVi?_gG=zsyHR2s=y-?W=+67sX3nk=g%iy^!*>A>L#T(zmm(ZT+XXFUZpHo z4uEWqSJ{9xZYdrW&ybJ@T-x8_3REzrRqsy6V;tjfyz6_db%)m522Y}zHr9tg#Tf26x;gmlA{BzT{`jf_AS#D*g zR#hN-Xm2d8n~JBt+G5;$6<|e~fBESIe(5HZ#8b&bmE!u$G5vAdsA^0MItowjxiM9A z=rFptay;;=_^c{AAEjATRn%M|(c%C?eQg*RW@iwXjUJ$O6L90)^WH)$FifGt$~GjK zUqv{s9d0=Vb<|hHx$hI71(Z&JDdeqqg}!=)S$YSF6yg*V#w@=B)fZ5V!>j}=UO`Go zDI?|dWSXNty$eD0pmHH7(t2r~Rb#M}6s(d;3zwV9f(4?|CxYS){Y5ASv?u?o>kc6R zQGYoZCtWx3}&is zJEr!?{2ut-FbEwP$<{9rSVX-u`C0pDHEoU^g{Ks@OoMx~5Y8z_TpiyH`4O| zd9@W4CKHZ=O@2EL_}fON-aBmho%?fxPFh5ru^LuTJ@`3pnDZ!d3$9@{+viZtVh_uf zEi-xa<^86LFs`sta=pyR$S}F^6)NknDrg-fZz{+|+WwnKp-eSkR&m)JI17LIG!2_K zZ>HQ9fZJ}nZ3T4@({uwPfOP->AOJ~3K~&G_Xrlbmd(gRv8sl%5iG%_dUjGYE8%KIMI~A7I=s1$7Lp1ZLxJrk zcH+q`wCiV+8gst|^f#00E9Jb=zPk(fTbr(9o%pWAGq({DO^u^SaLD3rq>z@&x0Hux zB{FSBJjH~_KtxZt20k-?g(H;#c>AqIEHm~o(hjB>MBKAy1Sa$e!-a7vnC*Xv76+!@ zlbNEmM(imBo|}K%r0GjaN^&V6$Ylf|_FpyARNb{VJD)l@2Eh#nI*w@tcxr9T#k!#V z|Lw%Qd6WSK=pS>j#a;lkKufLz{k_8>{+LP|Q-rhmGkL$V^M>tSh+kiI zCrZo9u;|S3PXWQ`Is)LMkx8aH_uiQ=T`)LJ!)$3E*@Iq>fCygG8J0=EV zUcgRx*OP5(zE;i?@8V}E)Wj;kveN>7t*gYYv(7rp6y;P2E>+zT5|#dB4ZRVy7t6Ek zMiVa$zD#;jp8jNzl`c%B<4tqpxLuky=cZFKihHf#B#C=V(y1`yOJ>CbBvy+#_W}JQ zaK)^N=$%`OmtT6?R8SJaQ%0#^BEYiZLVWODI@Ux-;K%(V@zbOd3<{|xG2sMTnj_iW zY7*xBq7s7ut?m<)4KFe$VeHQl7eacJA2T zubD|E4)_1+C#I6k2k(DqidqP$HD?`6MOdjoqz59|RG=91E(y<76 z@_=L<$1i6jV4m-K_*Rvix<=hOD(*o#5%T-=!ptL!uk}<4HPtRqQp=>Iq!S#M)>GM* zUViyylYm6tno6CnCNI7lhO&kPDZeQlt}k^K|5qCKTAbeqiY!WKGP5@`Sy5dk-N61T zmLALYM9-C*P`P9oKKkfGYV8Zn+<)|=9~se8$WPq1EN;)QuSfQAU)(aJ0h6O^tteO? zTjyq{9l6dk5LGoi_#{lqSLq7p>M{aQ&N~gpla3)H<3kWxwvP}^!L2>uCuEk_+31UJ zH`!a18=iQ;&J8H+~I?dKlvQT3-U~!H5G~!wG?*NqKfhh z0SGCc+-#&u9t=(O#FYc;Splh$u@SsN&*XtnEAWcMztfZO>ZWv5mldnpP&Iw+e?vfc z!@9z~{|opf;)v~63aMd(2jY(NN8^%!HSjH@;>q1W#QrxH&|ke+52`g(YQ~Hixc>U< zPbp<33O0QBaEV5SBTx!5t-Ivy*#sz6m5L2!H#$G=9(QN9MPuULOZhiG)=Ez|d%`g6 zh&%@eNximj--;dEw;S$8g z%rDX48;S;-as=8=5UTcu?J@#T0!V4egIEQ4C=IQl-UJ#QYLVX78Z{)@<-T}fdm|R@ zu0>w8HzIm)a0ED+Ok_*_>olwElgk^(=M~ZX@+2w0YU(MLeoPbLk4OW`M{q`24^TsMC-E)CxY-yE_@8m~JW`>&vudM8tw71!~3L;wmSS0R;@ zl$2JVEN2!aJbnND_YI}gg{s`66k#XrcdI@8z&|vtYXsT(d1v2>K{4|gVD(Sz+_A#| zzw-G1?sva4wHzF&Wm==bb@e_i{c?fLqHVj zi5)#X^IW3-S{0WOfY>j|VPQdjj65o!+hlX<*KCmqRO!{B_{%0QyuG~y5xtUe)|5W1 z31>re)nUB5J{^np)td-7v2kWI+dF`qXI3ex{x(a2BnJ&;d=cA!W^&VlIQPNNGQlW` zh{{VVkK6_11RNZ~Nh8)_(!B>)Bc?bVHz!QM?8_4IaZCwTY%a#J(gvelRmm_~@_7RN zX&<(+)R~Q`pgk}wxB_D%>XAsm=iYq}_<9I&L2X0|-Yyx3r@!8aqP#2}T}P8ng4ByKMs9&~e--)Fe#BH(#&DhabFWbVtnKY|6?h zpMOg-yG(hPTyinF&2l{O2;H*}X5t*4b%~gU`)-=M#Om^TFI3yTScB5na1m{-sbe-S zBLD%vOk6#DdcePqq`Q`6xU~br?+Y43@dV}E|L!=3xZad6=gdMZ6+QLL)Y<4U023Kh zvgPOwyiFt^DlLUhn~ihAyHLog6jp6D#b<~^_|c`8 zV#AshSe>75in=J+>;Fs>mF}6sDpd9OLN+In~799Q}y&+0-_xSG}v z4XNYOW8tLyrIee4FlXj;gXuNss(Na`?a`?0H5TFAc)3=lx5QaD8;mOayu8aY8yVma zp^NWit14@o+ykGfgpD%6DI!pufKk#rE#66w()Xg^ax<$cVK_QOQ^xdLbTue=GLy-DP~US-3;B1fJrr)M2~7 zNpz8ypW-)Up_n#lEPg$y2NI7hr2?gmeeBMmX)S^iNEoQ$F10pX6iV9vcIzlKjYH-^ zeBTcYU8MjQZ z^gYtHp@D^Aa~ay6k(rB7maCIPnJdj@1fW5v)$_>>Ha|X`w_0DF{2T($p-O+ev9p$# zz6Mh!O)!Iz;>=EsyK1Z38NZBxp70KiFr~Y85}#CaH>J!|LSY4VoOtnXd0BmZ*rNVI z1d7U%C!I<%8#l|A#^E$7Ici7=IG*Cbn|m#P?(Y}R=|bm^LIjT0;TVj(r91)WGT_Bk&#R~RyvN($IZ^)Dm;UbUKvD1VL1|+6QI2y zAjdOZ76F8%gam4M@aufT2zkd1g>-b}c(>cUvFR9J%n+QClt9gHiZys507nxd@TR+z zA9KrzonA)!uF8H%p{JN?C1VhKqlrq+ODkrWXS(Hc9Yw9W+&zCx;5RP5-*@Zi{?vC~ z6GyiHLW&v-)>_S{sQ5w7|4^aLm32SQ7 zcI(w{Bga{sx)BB0hmEt3Tx%7g`+ug1fEFIYpeg?N9ZShS_Y5gE#WIJ_NL%v|+-Wb1 z=x;(JlLa2lTe~g1y_jDi}P;YbgjM?K^FB_fe>%X z_9ry#{McUeBA{Q`#|jc!L<{B<)O$TJnFxo-rn><_7&*N*43F*&z$Y8i5lBUZWIZB) zR4R4ok>LKOL$hktDm?q_v&K&$Dk>V6jg7#$!F%CXo$ofp*XqoK0FE=Wk!>w}8!ZL0 z4*zj-C~{yqrLoQ-hEU#Mg%~;4Jpmk?3wO^1=4K2TA#(Oh#Z=;sEqq zml1&SjH~LX;-$+#(&>4s+*0G=k39`Z*uXHU0DnL9;$Xz)iT!Wh6cuMzL3K7w=&U3b zjpVtMi>Q=4(x(xQjJZcqT?+G6kjU(KD=MqQ8_)Zda_dHN4=OpW2r?BGxneM%;lMCv zG+p~oWxH}cBJr=xc)Ydw2=l`?f`L79x+$Zt|DEjp)2=(5wrbisUY?w;l=5G-mir<=tZ`~QYy-Uh+dhNym4ZH zo&Rv$KLM{~^})Ya?Ii&#z@?X6W^B9+&bWoW@}#@J?Vx@6!3Q72x8Hut;vHeQXhI@x z8d!uR8-ZUbZ%${^IpiMJ5^$GhP>H*>P9Yd(Ps@qXv-fYq!B_74%s1$6~|YP3YUF53ac43S$9it-t2qyNm!t z{pF)wka`HY#TBRws|S60PP8hUKX!NyMotx3{@Mz7Q(N0oopM^q85Ku>+P5w3#?z{-P=PCej%cJiIU^iQvC4ak1N&KgcWOL2;6!phf?6WKl7M8qRWJk}uUd;xTHCI^`fB4;C9d1m z<}w11Od(9QediwJ6%?a9ks4gdbplAXDq03|J$k{D1uqMw<#%!oaUcVYGR zmB=b6HAx#pg=I-|^kq79$A8?Y;%c)0yRE{MqavHC#1n6Q8U^ zVNQmr)xKcC0@X`c#xAaNrJJr^qj-e-@4w$D{(OnB&z~{`Hx4et5DzA5t>wP7Gf(rg z(={$~ku45e+?rB!iRv#W?oq*vIf+)L9+h}g^&CY=KC=z@mO7wy$E-x0@^TZ$CtZ+o z_o>K+EI%qQK9~0^afopMawGL`Y%>4IxoztfmA>0S$QLjW;oE%2dppIfM6{d{aZoQM~%tgLwbl_l@pLg-`p2^M1~~!ao)t z)=tC=i`S!`?A6bHaxH%T^Pd~5iu2>z`|L6TkT;fMjO}!p%`GfNrHy2pNN*6u}{Y%@2)_`p;Qyh@xTKQ=sjJ(^5xbhB&(pMhaP$e zpMCZj#X?V9IC~;)8(xVK4XJd!J{kCxJX2;@mCh1ElwXC)tE)nhfJ>QkrD#)Wb(Pgp z(3V(lqj+j%CRzsdtI*y}N139FfWIW2j2?A53Wj-X8cnfe-Ia!0>n*PDhT-(&q(SM! zH0pnd0F+fndLWsA1qwP7%Ib7UU*OuImdvkgPqG+IiKFal2QIRW!moc|}C>DtwMq)C^|uUvjg zK%)Hpmz_TszZq4JA;s%qlfzciVbq)SD@ugqVrn@$t+nbq&P}E= zHkuyTr1S>~H=_83>`Mn-CSNK1MvzmBCI^tp{?%03S>B^61?HHW`Jt2lF&8u=G{f*g zVKNryNzYXdU!OYkjEH8*tw>z#y$X7R;%n9LhS1d2`P+=4GXk>rCqq0+1q5t)$KyOPRvW};^pzGm#} z(ZXU>({||<7KrfLEX=O{3eonX$oIE{3Teb~L+>2TFv&#T+;l)qNACEKhe;WO9^YuC zj`}4jkK)J;pW<{NJNjsu`}UUv;?EmPuxUpQ`uFQ=N@&fRHLHYxc{W{GNw3`3WFC@V z|C3KXX`&V71AaMy|Bguk7+0Q>n3)6YuhYmZe3-HB4(^ zt?5~9xPqQNEJPscFHTLy0EvrA$?dpT+=FyC%Z^y3#V7Z+IEAJwI`vi$Z|td$#lLB_ zd4E$jb24tFn=&yWn_O`7MvXz;@H23g-zvm3=AMEC?6iE^dAf zv=bv~=PWt03yb(D1HZs+3*|tT}&t3T_`#ih%_y$;&s9ORj|gU9k{v?y~^ZWZ>cbQalAR zx-*o4K_Njs5bW=5s^s`IR-%sXx;e*+kww`+GB~|VAtj!~hKHwE$u71KsV+{pGT-bg zvPx~qJojU+vA8SA_}VU)5Ipxnmr6f;l9!(oaWF|jqpV~ij!aKdDt4vGWc$Xk6uh_L z5RPVNnDZSP(bM?6C`NztrVWgLnS`j}V{oa*cL-=KZ}zwb0|q{HY?GL4r%SE{7fu6j zc8B?sKkJ;HnP}~g!rHfS%XCI1YDq*@`nVeiD<))K*noPS5?e6pJ-x~HpM|^byxl|$ zw$#0=-?@wcL?vVflqI07lBtJSJ)u6tb`m=%APn)W!jBWHurG&{fs2<(${{&h6hV`v zdw_-7+F2h>Komx@YCXdogJS!@CyY7>E^wXQlj23S$=s8d$sGE5M^IBb z2WUTN5D^_lMbnvq1;;Ij;?cS*Xw(p)`8R+$Xis2HRRr{#IGHG25(WT z_6Wez%KrF#cM*=}95G2Wl?T!hov7T7a^c=g+Lbb~Jsn8Vq@`QqlEIVaU8)I6jo*?f%1pxUyCDSl-Fk*N~r7*;^o!aD#J0SSnR z?}0OC&qQb0e&=Cy2mvS|hJrV8ckV_GOKR297t<()oGB?MHL@2o@Jz70?!>k!Gp}J` zbe+XrIgjDg&%}UING<0kAF+pF@R^rj;(0$qecT{q=a*uC+7awOun$?enb^0x0U6}) zE13sf3L~*qa`2g*M9j%W6?^(TslQ}Og&hy&K{b@0bL{$w3Yv5>Qp;jii>t`Ap*y-iiG@WQ)f)IsQzHvW>*T zW+D!S0F7uB40^OkJlueC(_oa$O5XK`%@dJOgb8Zi*NcJ z5-+|QHK8F``1)HctnmT%(AJ%ik$$2V8?{TGRK}!IrM=-OtGDbS(uucRhS%gUUpx=3 z6j7?cV>V6;+nL6VIn)q^NB2bGl{FcNW?)BCa8DsOVivpH(V1>tyho$9sT7+izY>p1 z{5gBlP~0`1*>Y`J7UefrS@OOzCjECGDP0rNRdyX|i!Z$JLf4!{d7)!ga;uxm61Mr)=*50WK&v;VCRovHak63BnsC@5mYh=aG zef2TepHs>JqjD-weTtVi5IeK16j$A(B8ahLX;Id+}M@u$1iPXwx8`Dxca86@!LCYGgMA#x}SXF z2^?o$0!2_Mi;7~NW%^J|xtxwN8So-oxL!*$YOuD6AnL%}O1Yo8OkCqYS{gVvhaiu~ z_Ql}km4^@$)&qaK_g*<)6f#a?CU0>k@K>{cKLNiAcE0h(8-_BSHDe;~oF0VnH7jZ7 z=fY{-xrU0Vcx%6v4^hA`mVfHhsY*F+l5Xm-cdU8Cv(GG?_uC-a?|+2{-&%(wR8&cy zBNjY7fW5VFU_15<7p~^~lwM2z8{+na>}7dSe26)QO@QP=PbjwqB8Qs)s;YD}(2}Au zXhNt0d`HvY$^$uO%ovn1Yw`-3A-?Qs$Mu6r7VT(hIiTy_&DPDJ0neGTA? zO%od=9fjDn%*-SB{G<2rJe6Zd@@T*1K8RAOh=Z#v*(oC>@E`T+g{Sr<;J<6r(35Qc zUluF?3wD+uNP2*g3Mr4Y6 zjWWL~)+mTez|ZVMCIy++sk6-|aUAAc`&)dm{dQziV=j}T3Tc{kRkz)62HG2&9;3bN z7fvo9n@E6jYV1S+03ZNKL_t(yfJDp?A@>fnO8BF~JB+%I5_6sf>{?e*RVln3h(PNl z|Cf>U)?2<}88)yK(8VdtY8BWtS-u5&+c7xOMqddV4(FFKdz6Q%F5H3Pw0|kbp~Rmh z6%9=PUE^}n&-Oa*5CRaO$!$?-jMi`1h_$6dFt1lBf@;&cy!rC{Vg#{q4=t?o!7Ue@ zhoM7;8eeIp+g6%r-uH7IMnT$|RzE(ac={u_}w&?{vzk1Nc=(SyTdl z8NIGv%pQu@S8YXrpAQv&_bNSA87b-6?2*EX+Zr(|d=)hE;)^eu?6{Ks=FgdldoLM@ ziFvQVRz*_W)N~}}Nb|x@%hs=hthxrj{N*pZ1pER9`5VZ=s7Jpk=-F=^Y+Eb@APa$m z^KgvA-S6@ioIrV5QKS;_QvQVI2|C0xRhY7gFUpq`3RPh)xN_JtwqU*j2!%wmcwxr+##>sXoBrsz1SUhymKul~J+OkZG6JYl)HXF+QqVPuj9Q^hD z&8TA%i+k?*gW~$L$O&D;PS(5FQD-g_Se57J=lj3&&O0nP@*kbbL4+rBr|Wx50!?3sVfDKZYCq0$@*d`FiP9?{ zzc>L00ucA3GTZXGkWXuDJONJ#z=@NzdHBF1B$l=L_&wLePEM)25uD6>N6sg!|w?yK96K&W5SRm+&s<))4X>Q_^Bun z`E_cFx|3PV00O_)Ixz1bl1M2!+jRIv{lpd%85x&j)knz7%OiJ2aG*@8YD|W!I(ayq zeWc6xMUcgwOfJCBi(ZY#tbilpe;4-l`$zPo<60oITia}21_6j;(%iPw?5FvP6A&_x zQdwo+;^PyLketGEPZq>^zO+EuBL|w|oE}^=FRlx*%E{M!|JY7_s;AZBU|}uNvWw8K zPal_4ezo_oP9XrvAG2o7g3=D9(plo`nxWJXMz!~>R)3d0c|8wr5t-hev@}TTs;2(u z%$Z}V#<(i)h}xZf_SqOSf@2Eqg|P_%%-ULfO6pf^{;;=0?feV5&dxcBtM)Po(=ChPQdx_ zX&^jnDzv$dRu2XB2r-WzKmNozc6O|yf|7@mlaq}iOxbIcRajhrstAYoj6zsXlH%s$ z>FfhLaGy6}Ok#lI6y!Q9H{>HUPqw+Y&HArq1Zxf5o-}-adbkc7Hp~znxmT+KwhG~D zzN-Axt5=L!(_VBv^kSX@=OzxK4gqK+V(_B}l=?EBtIij7Bk-G<(U>2a4!=4=xu~2|)8QqNhLD@&VKD~c zp2RVAh4ptlLMB6rV>4&YG%}|sq>74&+Zr-zEc%R?Oaa_+^=W+PJe5dhT5ldZo*|ih zr4TfYf4kBw@F&JpO`SbQq1R2DHX%Jd-RK9V&zOP4-Z4mOID%k1F^$tR8koN9 zcqtv@=&I=F?SUYQ$;860ol)!QZT{at4kf296f2L^BPzNV#*G`-Eeb$Av{MK`H})YL zqGAStG!5?9y$?^UC_srXs4E_4`Jt6DMwl6NgA&*&o zaOJsK&KuBb-&9nEF}s90bI?b?n=4$#sOl$R? z?)#yFz~7gY$5&y!a`l}wZWQi5yD!cTVjjO5E)W6zl*aXwm!A}2nN)VF$<0?{)XD2+ z{np<9V*8~_(Yu=Z5K_MCs;dkI)OQjKe7HC9DFYBPfZSS3Aq-unI^!O%r78!G$8>|9i^LcWSgy~ zQgp}ugT`yqBnxO|e2zZc@vc*#I1`nIQ(H+SC{D;#1mH!`9a_H!@%C&M)vU&@UAv3| zSqiqvlP4R5CoWeAP`ZK%wd)bbh`^H@bs;t%7QL-APHKLPNAarTAe7X!8=3;)iW@_) zyfBQSXqVrsOJHZIYm1gya4&P?DUGXe)H@FUOz(@&cOJ#r^Us#8e+z+V z)G61x;S1W>H!fSYtiNRb&15^z7&9FAUN9JQ>zBd1B%@`7)ie%8=gBQr?7v!KVuI0| zG`D42zqbFs5W8Gow3=Xix4#Gz#*W5#!dhoIyVjmT zCaPR{i6WoCHjsCZx)Grn<9UF~Bt2SZW*fGkfw}~Ro*zd1# z-zNh9ek#?*$<=xN+O-(pFA9GiTZy@)AHc@6Q75oh3qz3gL{*iR9Hx&0=cuZ%vuDgO zx|tR(clj%s!lVe3m?!5iWu;Y>+LvB>$y7*|d#v1&%Sw+U_t0KO1d)a75-D+cXs28# z3y*?m6iv7BsP)RFD3n0=6qg#K`6_Z#v3FA%>MHD*J0hN;%$^9bAA}DTuF3|~r|~di z8_0#VxEXdj26?dLH?wBj8Zja%lD;Qo$tc!#6#>|ZjB^`%;)R0|3|1;PCS7?Yc4zA{ z0uVNmN${zso-zd}7QX&EHtfm3;)K2!J2Z^gkh{_n)oq#uZ-?T%rO1)DnSvbT=PZAN zF14a`LdSX>DzsaQKXxY5k)Cy8V`!==_KU%*71QwS7pshi;{*5Ke}H-Xg1Yo<7jLGv zKZ8zA15~)Naip(0_k0*#mkuGc+HwMtZDxdmysAqcGdQW+PQ(aaF@tM_fkMHgOx8AR^g-MWkb90QW9BriERj>vOuds#3F zdITfBhPDFq@cP6dw1--dT;P$OuzIP$GACw{AlK z8koX`lwd&{t(*ikfw6e6aW0la^o86nKTdE2BQTpD-OBvx&UV{%?axxdN}UQK!W2Kq54f;g@d5NUqu@PD$t$daJQ zPf>iy`*OjSFUHobTaAT6x`80C2BeVU{m?Cddn*ngj^~J?vUybzhnn03Ee(!qCA^?; z;^!I0CB4wi^kq9I#ac&~5gGfTu*#ejy(k8hqJK;w$_w5XsiBp&bB&cZ`<@eL#J|LxJg$=o9Y(hZ$? zWJm8I_4JgB^Yg6OYbUk8aY`@zEGZu$HVTdeYFC<$n6&J)Y1%y)I9((NR3f0ZjhO^HrTl%fXNQsebI z$vHi-nT#Tbe8lYUmq~$P8H#ezeJ|b)+Bb4(tDbFl_N2 zj|VC&&T?88@yUfVL-x6*9|)xcX`#_oOj_~@_|B%*V0W)fNCXo_}0eei+DTxPe~ z$PiK+9(m*uR%<=i(HYT$dz&cd;>C-Nqfb?NDXyQ9j9>OUhHwv_S{H#|Bb1aQzXlVH z$G?^1Fp>#ZefE4nx4^A#nS?9=QV_~YprkKD!kG@sj|1uKVeh6<5)MsbQROEPX38Nd z&l5UtX_kWp#O?wXFwp(t-_19m2N_3+2XLhHR!n0BS4JN|0Sa{!Ba6 zM$^v<>`?JewZq3s@W6wQ;?w6JMtxzr`Oc;Pn3o`X#XBsF@g07<(ux(=eG)G&?q`E{ zbRT@uFctT|yNUS&ZS*qy8|KcPt4qiP(wEcCO~5br(z{t5wjlo)u9(~xcMix!w5_N^ z!0#9)v$GrJss5HSOc`ZR$CF>z3B>8>w2gO#A}c7Sl~!gNbcs(Z@2O!j^vqBq8Jq#Z4p_VbaS&mg!jnZ9;l7L?z}2Q zh6ZAEzi5{HA{JL6%Z@aP%%#3j_^P}Q?)$DBn-3nvO*j4mx7~JIx7z$$8WL%5_cliw zB{?f>-@FmKrp-b>pDdE%w%O8N1i$OZhAGM9W{R=v4;A2!KRt%cOv-rn<=0@qxETlu zk2-0vGEXT#p4?NbSQq`sp3V5?-B++~Uj>emHP$w$z{M2cGj1In;a> zzYHty><3=l$(jS?Ht!(t%ZGqJb-eM!R0UE|ml4Wftv#4OCI$WK4^aN+e!AKr31$Nq zFpo7g7gr9)i>nSJuegeo{}!fhyWZ`H?L0e5v0XrFA1t#k$EQTeUmam7GP3>>pMFK)Kc5_D zXAxMVe-Jj6hGX#tWd!!YIiusy3+t$u;e*;j08)xGTCjoaS|w{}>?!fX>lu;w`T+Y0 z4#A)9x!ri!D*M*yXx)wgG-v_;j2SabSml!MR%7ebi!rVC4ekPWq>ZMckY{sKbg&;@ zPyhBDVp(GCT^R*@ePTSInkcq8ggS`MP3vx)eK;$L6h9&Gi}f$}=z)9I(Jbi^j)(4B zaN2=i;iU>{+O~BY&YLzGHxI2sKW30=9@6VPmv%pMvc8;W2-k|shFWr-`JNG|3Ga^{ z(sXuntE)JU)D7Pt_ei=q-_p28l8XK>H6Z}yD3mXNIDn107DA8}TA|X)kgRCXUyQI6 ze^Nf5pcbx9L`RO^s+obK@b*SeTAK23#oP&);GaPZ%mYiRZZ%$LKJ#x9Ma3F4Fr>HE zH<+R3aX3=$gB2N#_+(!tjxdLX+_x2Pp&&bl!qwFwb>%v@AOJb>C?r>PIS!}OTGAb#DECT73ekfwy_V6}IQ2YI!as>;%=B)o1t3O+WqS8u(WCxSU zP=5aZnZ`4rdJr=8en#AyO+}y5mrvy1vIso6#i~E38iO|u=d~1JI;8Y-Oq9B#>WD#D zdMpv2Z(501TD)crib7AHY}oxHm?V^#xPgRDFXajF3q?afBr3eZkyqn`L&c5Qn(B#- z>E+C;N#$>3G*h-+ZOpC8N_4uLTM&RIq4)qP*rYUCwqZL~OgIzg`Wz+{6O!9%+(aWNx7pcH#+Q+?U{WUDo~La=8fFoVbuC_fpd=|fQ~E)0gI7c?16iB z#9}v#cs%^zy*P(R=`=OX!T`Ur_`db_yEucuf9LEtjIUk>TV-*Vhaoy65l2){?^=_M zhl}vthMhS<%bRviEC?L-6W1OifSZ z?v<2?CF^!0H$DRKktqnHDYm{mkAXo&C~EXW3AxT9qU2)@!8le{kL;X0?;6Wo#eP1xh1UuU(H3DZLP1z7v@ZKKR|1D5Tn>@#w?% z(9S=vsS~%qnUPB`@x&8P;+5B4$C#1B@yGK9vpDKWXe?%EmLAdQpix-(@l! zxTqWp-}(R}CQhT{$b_yxp@fM)mp_9QA1=fTOLIuU$Z7GJs!}V;YWA_Uxrmk1i+knR zOLM)x(>+93-L($v>BN~5SE7o;8m9=xGdylOGcqyH>`kIUF2RgLXRx)gtG|{S6$U#i z!tw0-4D8%jU|h$`7$o)D^8N4+3}(u5FNAtG&_v4k0*V?X)yxrKCnsRbgD>kg#?q57 zF)0P-Upxxq8C@w|nc^yb$mTW#ph<}fko%~LSFYKcg%4>SytXHIfbPO(6WW`(HpqFC zxM^bh$?RK=zwPv&jC;SX$Lclf&_6zsMKYKc%r_5z+}aCyfdlc(eSb2s@s3;E`0g03 zhkWN18?k87e=)j$93D6)22+?q(DPWT1rY5aKo)=z$=}&2w{}czv1$?}x98>3yN8iTPmQALk zfZ)7Y{9Mx`8voi-hc#emy-3XT;>=(XFv43>}W=|MpO~CQoQ%!!$C{I!`4f zmCV0z;cFP15`jNVYQ)ryv0i2y|nNR<-lgmoCU8NJNs;V|6 z%M`8YP@J~W)yY5Y{ggcOQ`ov<1)f}2kELW?{~Dm2cA9D3vaO;Tuf{n?)dfXM$xV0DGFlK zyW(K9|N1NsN^!xeTcLuwN;2}l*gWY2zrdXXQy_^FD=u5UoN22Y5t=fJIo(;{sPtIp zfkCG+BpmORkHX)-*h{~cDqMT@&+v=$CgN3^4l;ws;F*6s!Q>)IywL4U%&bP_rL@wj zVekT7d`G54;ZGA9FvT~Gk$vO`WwGEv>h2~*vwYqaPoY2Hu_ zZRHlrRk?xyDJLbd_nEXI}2pLI6@u>ze=7#^rKv)1mvxPkv%_0jpN8#($S?!kF`^ zIpt#6s+#z-jt+rXeV_MWytui_qz+e-#Pct@1m9$5peki9{`K3t46D`AZaZ;3kDrld z{p2L`>Z`9B;2%mo#2+Sj;0&+*uqkHO%}pigM^F^_<3x+veR@cm?>dvtTTW66;9(Ub zBqb%8gQ`6_feh#rWv{32#PJN&t$YSYzIg**?JL7usaCdK>05+c?i_0Yl=Kb;-8G++ zfRVtxXs=a@U97hPbVTJ<=~gB~$>HJ#o~B0r3j*z%yjJlUy`_}n96H*h_!RKpzs~X- zi3_rv$*vv*t6Z29-1TGs5u693O|?YEz2u3g(u~^__z1sy$4G zJJZCuP(vL^=SyAEcNrB|J+0&9w4iA$&arx)UK^Qy79L%_7yG{StZO6^-t~<$T*aSWm!zHNeX$4Y`h4U9BZx|XaSb7n~T?J-uP^Jzbd*;Vu-Tz3fTTI0XR3= z^6MbZInjLhS!{SJxs^Rd)XMW2Is*J>fYJF{r`t~4uMZl=R*qBVz1`#{`cbFw%Mo<^ zBJi7tPj%L8tg2M~V{!*dLI3svtAyI@1Pf`lgn$~D=Re1@C*I5Kg+*(=Vx_9VCPTCm zrTqWWP74B%{G$xNO0TW5)=O4xCfOW|-;EuM(Shj*tfpYrj+2lUT)>}c5626YIwfrY z03ZNKL_t*hV({j=EGm^687XzK$$#%)y-wrCaM0+fxOkpR@LC^Pr#CMO|S@FGk`iVHA1O?e$hSxBLQ0e2?Dh9Qz(NwSZv2y@Y;do zR)koT$VB10Bl)*H^+eGH{NghHUwdZ)US)Od{S`9L1VR#~3`}7(41$6Ravi`rP#k*e z++L^JYH$7YvAxz#x6k){*502Pz+aoki z-}PFa(DZ+pBOZ)5bG@wyTN1VgM?5QCyFqBZEfLxF#Og?UW#MY;o|0+p@+4n z0N403ecd4pfC&K{gh7yS(!VMW{+$m$v#t5*cK6pWwkx`Bwycotiqy#SaPfKp+$kg0 z@c!EVuq1o2XrMi_u+aAJ-R)`=_u?0X|U)%)pHUdn9t=5>m+#9GaZLKprT1c-(H5=lmqYRoIv zdE9L)_k(e7L6MSj>V=*2e5ux;FiDQ>jU6-69=QL0himCblwQkmor?c!Dm$jZH8V0YT$Nf}ASA}xY%#fv?vZG_OZY38f;H|nrpju6g$@*p}d42pN2MD zFytv@@BP)IzqY~xB}1J)#-?>$Z;`HQ&q*8+E8NErAwoY3_Nc4rIM9)J2L^~}fK)@s z#8)8k=mJYQw1STu$CzmC;v=e$xc@jEJ4PA`!DOvnPrtf*0E$l@ zK^Ps3>j)E#zN?K%wIz`g?7uW&Z!TPE7fzaJN)zYO-nl-$Vs9PB0DSX?Jpji8APAC7 zqMFW`JKvV9+GyvWGuoyo6VQZSLv2Jtr9~Cb7uqMzN_$PVH@1e@BQhWsXNYI0oSFzVw~EUQU8TR1h+Idamua>6krY7}yx%Mex%GcvuEhclPKMabf#$NW_l^ zfsE0NmvLY-fLXxEw(I{G8eufp8{8QEUa;Ahx;7fdKFvX;`ef?>iqgm2s>0&!)4CD% z_^0Le{-X6ZLA4eB^B2GP$`U^_)8Pz&i~6<$6tAHAF9k5k$^PE^vuyUK2{u$^!N>LQ zZqa*s+CG_BzQ8<+>-a=U*x7`oCR1&hAVv zu-IKIJzZIg>|`~F;2ja+ua}4bM5L6$VsA|dt5bJHc*krIr^b{Ls~5}z#-ab0i5aYr z^d&fkc=!j$dc>%#*iMono%7{ZL``>Yzd-k;{y^VB$P4sD@cRc$=q!z{R{g%Gds-?& z28=^Ieva-ZRQJlhzd0eZoj?u&++QSe3_$y8P%++aO0KSJr`Y@`_QpFZV@b=qp zJKy!^OXl0^6%l&)S`o^$qGjbyA@S%#4?R>Pbm5=0rTDhKWJz~q`-`&U-a2E(4EuEU zdv^VVL3U?9C4=0x_@om*1wY=;Gy9c@f4w9%(h=7csys}91eOCtxVQj)5-i|EG(SdD zN^s26t7Wm*{ySAH6mZX#B49`S&520IqJ~2>5FrxYUjySZLCoo*zDl;S*V~^%xDd3j zDN@;XOJYB_#>>V-pXA^pQ7cJ>+-9DGePh+RobT%c41qJ&sUNfx;{55JxPG<`$D9ND z)(J3?6=FUBkN`FTDW|Exw=FA;uz728Eh!<+?z;0X7oqo6GWdCSdgS|QWdIBXphQlN zSMCU3H5PTkX8~4_iW>hqUIenzPMbEZLCiu*qJ2j%WK~DDKMLh_55E4lUwzT8%{*v# z_Ss~~W!p|J@oSN?FP)`s9})2*t?rP88-F@mff*v8$5wjovzrEM&)!o~Ol*+JfFN3J zIsi-i%NsmcEYkMYVPYui2LMfT2@0{3Y!k`5TUr8R!uaO5iD*Ii5g~{LpE?CoGNsyU zug;iYW~-$g{ArchYg;^KLPXxZIo_7R@7^6o(6Ti#2R<+u(oo?80+YN$zIfMQNEr?Y zslW6_yc=Fq0TfL}qf&B(g@s#cN|M9*pQ=23zN?-IpMf4}hmQ@oWWRp>EHg9HrJRQI zl8qKiyO_iV{k~p^s_9Jw>qz1+*EN?^96WdrTFM)*ykr-3-)*-Ks<348zhR|fm7k2J zejqp>@uN=T*ZzUjzX6NNXauLsWg%~DzB#aPJCfV=Na5k7>6 zeSjQ%Fdl$GZIawAF3c7KgCSZ8W#!7GS>RSYRzvHUTjbdl{#EyV_#C)hM6`v)BuXRb zB6~)tTZ6~y@2l|p8N&cHnObJ{v55Q=z4S;eTP~O3`sAgh| zzx2Z2YyTzage8CwI05Y(O4@E6zMdU|aX z0g?}ojJRkHq+p}4rauXc3J>9D zPftnWZ?${JR9IHrK{ky?wx&ggs*Hu5NVuJ1W4NnOSGhT?-o0bxvWkD5&Ut8g zZIQ0r`Z7Y!_{(#)X5k$B(eQE`-77*BV0u_=NSO$sX(emHS7@Q5jzh#iQ%fp5)xTw@ z{J*fgUd*AW)zg_(YLh1Rg0YVPnJ4QXU6==Uz;`f~h6VN$B#CW8{csIu< zV{IBQ|2@%`D28&g?uGlBt+_`Fz+Qpf0b}XBB6$d6yx=1T7!o7G)k&nFixp^kd<|id zwza;ey}hDXQLB5Eq2~&RFF8}gcTaTQTAm+d04--k8l=98*}s3k0(@Sv1#{lDe;>A2 z(zMYY&l{k0ST%NK+#U;)X&dz121%7?*9cIf1xW5fn)6yHS6pv_o~V;RhF2h@#EqAg zmRY{w(0kSY=G}X2^IDbL);Xx_sHR9Km&zI)q^+%qxRBIYl7LxVJ*)uX!9>0hmW%Zt zHT>SuO`63qh!Jc*FC=&$jV(ldACn>Z#@oE_=70NsKM9{~NATnSyY2^24{786u4jP3 z5nBoji=-uteWIA`dA&Ra={y-t#E)859iC_(6^9Fmwm~JqQ(eFmm908U3o?KspEOG& zk^rBjOBUPedGFZW19n(WVx#?hL98tAg?9JZVfM9btuXz=qWq``j3_t{QyyYHl9TMx zDO2r=E3dNt@@f!xL!7ve0gy9*j6olM_@T|1@vLp%u9d3>)}8QK7j$zx#P7~-defL3 zk&2^$&Ld42nhC@YcnAiAYwnoRSm$G1&l3ZIfL!qpD5|~VY8u{2{m{L#rMNiL`+Rzh z_Z)~a#76KhZ|-#@8y zsLp~6;K&(enJb%4l_SzCr(SG*4qQsABdz^uU-!OKrl{4MY(lNh?iziBek{KUXcU?l?OPd z-A%+x(a?Wxkne=Svc{1vtchAGH~jAZ|yp>YPmVz1{cL@woc9Ryb_1)9L)&d*G*k`tkQ>t+3L+(g)`}C*Is+=Y0uH;8(T+x1{r{# znFatdc*s!Ou+Ho!v-a7$i;68Jxtm=*u8*CQRALRv?uxq(4M1zqx=F;`anrSS>-X-r z?BSy%K}3ZC8nq1O&70?51Tx+5&2QM!&*s`o|9neGzXqqSUM=Z`#(msPmqprtAVNSh z$(1{fgrT?+FYBj1VQ+p2;fU*~j{_q7g`Q&KaQ67_k9pn^opkJfh%Ow|O-4VB)0cgS zU?XlfAmb7MEjM(Ky{=@3)vBs;`4yK_9lB!&eg<6a%gz9xzrfVi&IwlOZd;;~WZ?Z* ztXOHuslDv_3y0c`nS%Wu%2$AjG7yVEK*(|NNn?lE_kQqz^&KKgYeUP<&UU#G&Q?Bz z@#81hp!8H*ykMSfEZ*&y%B2}nyJh4$E~3*wKu)^?7|XmIfke-NB><=IkbcJ^e#Wjo zu>J#=1!7B}4J!0;jfH+Xvat&E0?O>*%v`(ayFkOJi|=W(cct6f!cS$V>0wu1d1a^Z z{WqVJFFON({!VHuEG%?{|Ln8>tNppkcV+gqJ0=gXo3qNSca?N>`H460Q|T!Y2y3is zbc9Wvc3qo^pKSrzlE;i0b2xL+$g$_yIbsHzi{E!fEPymQFo5G}nu!(L&HmqF4;(~* z4)Fq5$^#+&{C2OKVjs=c0oNU%qc%65M$0F13_@&k-)%XtEewu+8z$M3HS@*bGwr(Tu5&571D^90{vpT!njUU(adDI6e^zL) zYP)uPfBXJQJ-bv$+&47(E{o>P@DD>XknpYme8ZOMaITiWAz>n zVGO~vp+T_QWv&0yO0(Bx_~ZZ6wcr1qb|Y@@-o}31rNl^f%J_#X%}2!SsX@|(svp2i zjv2J*6Qd#SP;v__mKIVWBP7g`Bpn3YoZs8CMX)b-oUIM_b*c`jO6X_r*N(JTmMn7+ z?W?c8+5vw~)%DKweuE4^PYA%z<9 z53zy&ryoEyWb2_wE7gY}WU>q4TaVNB!ZqeXuZ<#pAk26kU>wOh2Bm{s{qJq_40LQq zaLs{UJZ<&_`2sf_jI-yL8Z%F0~UTnv5($}8;Baf9tgqYv7MhB%v?>;KM}n zk;9+uK#sf$00v10A2fmmGMN7KGr1O}sQ^SuQ(x_2 zPVKrU-$@!qt&NE+@F9DSni>E<6ey*ID|Wnl_v8}>ju7@tqRx787l??27Xh1oBiN-x z#0AiY0vwxVv;*FH+c4qd`#T#~`=qQG0QQ5gtB*`cdlHf?6ajD5wbzx##RN4~10YoGSKSF??6fu#% zpE6t^^}Z?MyHS$c!H9KCyPNJCquse_9@Mr(1YDpOn0vefpYQS*0zpro6p7hL6Jc9K zB2ZFuXLoNJy1t8-A&`te8PXKM<^Sgb*($^cvJyNGg+u4Hd!TJQ5#rw$)zjYEKiFn0 z+$!)@h246~&35CBH=Z%|`Pz0Tr{<_m%>bxcOyP{Q-syJx#l!8qu-VoHk=9CswvcQt zI!?%%gg6i8+sY}&{OOQ94n=Z5HVFRHzgJh)`x9kzftF3gM&QwFGPb$QI{H0X+l8wn zv9b8Kx57@#%lLQ?_6QP+0Bhpa~I$cu^)>A|0>o$x%(msAh^|5_lM1&(-!~o3Rj@kbYXVuT|oQ z7{H!8Bb}y=ZA96wo$j+evq(@7IR+njOPVgVsx#ESdTjIWPCy2y1p+U2oxkXQo@CaH2`(!`_1p=#2C`Vq!@3ch!a}^^$1#H zwto9*p7IYQ2GXZJwV9|s@CaZBu()G|@5n~p zLACbuA5yQ_?n6F=%-Rw*et?UMiR8MgI`t{BdO)7MsFZ!SVeBJ&cjo_N(L+VSM zroqpKTEoLQhr|VhgTEfZ>s*H?N=QeHwDtdxevFDjSpOAR*Sh*oR~k1=g4zHWQPBci zfRev4b8Pb#=opTRh_brKMBCq(XzN3>?cMwYd*R*Bte~ja1`irwKm6f?_Kk0R!^k=v zY)8*Pr)2AO*n(#E9_mLih}YtzDljOMz^xBt9hpDoH$X4IXu_o(l}E z9`!(m)h^+Y79t{-t|JCgrL9kdBz>CIG)7vf81T;8NX2DD+tS_9_TidMwr*>o42z*Q z@%#y{Bt4o{^SF!F#@HEu>$D7jhfTH~j6(0e`>xV-71|qXD{bgm>DDJw#z5`w{Wh&j za`2J(lH{Mub_nezT=`@F{agFdLqDu|zlk7(pb;hkiTdO=H?2DNeevRPE>SNVIlc zhJ+roQ^!BE+5-iV(i>x-vl}!Yb6AA!PaJ3q3TkcT)?F4Rl^#1yT5P;EmKRy&o zV6{A9u$5HD*p`yLRP%Xb41dFh46!S&xWd5r!(cJ?23z16=+q1VwUDsf z%Pzaj#Q}V{bc3CrnPXQclv-q6zQj=T)%nFFvi}AjPwH-pT;?CBx7S{O)4_Rfx%JyN z9Lb)TsMz#y2{BJJ<8ma^(9WGZZQHhOj__yAn&oODVoV%8_8j{^ci(1L#C&cM3nV7F zw7<10zCc=UJOMt09@cc1Zu^TNp1Qvnlg7r>kOdiLz_r~_8MF7Z`yDR<+XSKui4y=Z z2=lr-NAS}$@7TldBHsNeqwU4*8TR-mOKnqrjbPCgmY7)NwhAGK4p?A$psIefXL-$r6`R@sDav456@TN(>1PYl8}jk_{2!`>;+w%^ZOuIG^JszfCxCp)-y*`9I> z6@!S3>23*0kt)g(;Zk>zSOskgLuyu5mir76e$$+`@gqbk>8V;OZ5+bsKa32(zy0lR z?RUTXos%jLPCLuKo4&=O4`_w^7HNQCQo{NfVdKHNgO&F+jEg-IW35}VOypsa4uY!M zf(GoEl;SD9Gi>~X^X%GdrrX8ZcBJk4R2%C*OQcFn`rFToNZ9L>@;h(8h+l+76d;87 zwM@??2R?yHKV%qoeH>%#zOf$fC*Ys=^2{FkOomW-O{w%VM8u6T9&Kd1h@UM2aR4xb zMA-@`81k*5Uge_0#y?h`P*wXQlkN2bIrh6xR0Uc^Sg0DUBCs}KzyK>y24(6);=Tj7 zL4s9YadB~O3+J#L`YqVnHv>Ti&@^wWSFg4QAAHbe&V1j_9-3u85mj9ly~@HXrN6eu zB1Np_Qgg8fkemL~BCjG4VX;Rv5l3{im_oO%HfnH&U3TtScHvolt$$ptg%vLlRWBB` zNM!oa$zajjrHsrHKf;K%PD7%`JqAG0qEUSZBOvNE1v)91K^ib3_*xzN5d%EF|3KLP z-0T(nBoDvK8K5zM7z8PaRzLwxl8O?djH}Sq;?r-3#}pZDr3u6BA6vqt_Ls^zn@-cyxPaAjklkW{p(fFTVJq{q(0lHIjo&9Ghd`Kfjw@61%~o&;%f8 z7O%h7w5ZVrD8fM={?ALj!Wu|R{N5?CT`W_9K10%?Y?vx-WJr~bh$)ty000YANklV!k0WF6yA^22+O_)o1idw#u_trssqUrBn(jA4+ZGB25ck;YqeERvYwoA-_O z%8(rKwf}9JocFOp1Un4 zJIkhy$+GLuinW1dA6eL;CVYONT@#C3O|E`dtODZ4Wd>j;Mv@)W9_~A5n1-@>!8~~R zX8{5o8_Zul}&YD=Yq+NeaS|q4HSU7(bV8 z+gDBFW^3Zpc#!^J5+L{^4Dj45jmo=%U+0AnwPy-@+uz<_Zo7Av+SDnRIrGV0y!%l&JLbY?;#Cr5xxeeKyHr%8!Je5h!&a=@Z0quhY~I#UcFV{yc417e^$Oi% z;k6YO(jXnUrI$d2KqJ5PG8a;`)NvFk@*L8`r&iTrew+e=C6PC zNAw;Lf*;P+RzcGFlo3CJZn_uL{usTEX#D28SAGDgC&E9Jm}SdC$JifdZm(TeLLFCXCFs ztB3cr@ja?6Gpy9&>JM0Gb-9>asA$KT(sAKujD`m9&wo$`h#D+%dgAMZ`Gx7O^l zr5g%_hd5~2g9h33>C^4zn{T#^j12Fb;CqM9K#&0(W6BBnC30`(%$fGqTW>i=fUTgL zQhAT;-_yqR>t^G6)?0S;J{jdgET(#gg=&k?1rb~;BOiu&M3LlLK}3EYFRT|V*>o<``r<}cWuR1 z%OaU1JXSmJzJm2PR+-ZAumszTca7>vp#jd&L8k;DiUuP-99ccPb z_xC{t(8ebKBOrs&oH=tGGg!QMv3&Y_6^jyWy}HNS@cw;lNUsDN5Ou)P1&!+^(|2-} zvJHjovo2LKksl`?fWD|b@YqVv;CGFD=)mFqHi%H$g3a|DiDG6mL`GHdso%eEi4JYQ+KG zko13HgW1m~D0n7SC;Pz``1bfqb}BWuRR4GKyxf4l)H&qk57^`@+}CZWJzqA=o|?7B z3X8T|T1v7_7~0qVrB{^=j;pl(iu6eclNM8>d#gUAu;T`+35&7H@OazZ5G})`^6k|| zTYg2jZ7SYp8w<;9+umB+F8uEvRY7i0eT9sSbXRqU(pvz0h=B-N`2#+c4*wy@0NOd_ zE{|VPkrBzaX3ZK~DF`t61q%y{Y@fCWl?M-6xK!$l-f8v|>F>An+^WbOIr7`s4vN?x zm?P`Iq%f+yi!^SocB>!Kg5&pJBtQKBuJy17q~(4{o`QZ|b%5`9Ki6y!DCzC;5%!CZ z%I)(FTPz_yPDaCerAVu@l*Aaz?42S_Ce8*b;wQatZ|j!SRmkW{+qJjCwhNlOO+h&M z#idpz6jVh`qaCWLwHn1V5bc)kV3X<} zW@Xx~w|+|!zR^DT-~(H=dW~u%l)H>akr9y=E7)zcf^J|C5P3}vuNq-D|1K)rg} zK*4qg4<77ZKzOsVvRt{XhzPkPg6)i*fgl4o$&UsiBIyTt{-1vOY1JD1qm`DG3h&>? zesG>#iQNxcboE{fsj0LO=cn^8rmOVzFNL*#LECZAbua)L#Lufr_5kdQzt5y z(ACD|47Kr@u{OSUqYVg?@vX7cqLq?7RE0r8NHzed2rPjbDnIHzSbsc=K#2+dff0~u z+cg-6k9lCJ=Wk`jX$*^2v_Yip>C#md-kSs`(W)Z zE6m&C)b(l8ra6^=bkKvreYkGjIwK3vym|AS*PvL8p-z!P zDalEe-6z?GWTaWYxI;>KQD@x}VwJHt+NJ)As1uezkmHEP8jF-s4K+MWNiiC04_IT> z9t)||UO^E#4N8Yy8IfZ7b;-7JN3DHasQml##g}Z`;+P46HdMpKB^UI=chG}B!!HwL z03G~*u$~icynOj`Textcdu`mf$#(DE=aLaeE6HBB#CWOYNkS8bN)xEH@Wxu9^ujDL zLEk4NSfWz$P#DB&_KPtHbyz1~z1pG1DBD>bZiN+Lwz+VJOFu@!j8UUTxk8wz`$LBg zRTf|0-oc#~eCxzB5M%(S>EUA^*ekFMR(34zD^{$KhOkyeLU+jOe$crV@gy9o@`&3T zrnUo8|GUT^h)YmmHg3O9*e$sS<$#bP(C9Me^b{CrvE*HU4R$^N|s|Y;1hfSjqL;0cuI+pRJ2ln9Ndi%JTA9-*#SZoXCFfvGUYhz zCm%c+adUEVT?PWB~qVaRaj969V^P3t$v%A^3zj@B&@Oiwwu) z%0skI@LKxcjO!TS!* zfMy`b0D{XPI0Kz{27(Nr6F>amtp{fy$N++CAUFe^cn1DIZh%IwJkGi@00000NkvXX Hu0mjfMCK@{ diff --git a/NanoBrain/Icons/NeuraalNetwerkIcoonSchets1.png.meta b/NanoBrain/Icons/NeuraalNetwerkIcoonSchets1.png.meta deleted file mode 100644 index 1ea36b8..0000000 --- a/NanoBrain/Icons/NeuraalNetwerkIcoonSchets1.png.meta +++ /dev/null @@ -1,117 +0,0 @@ -fileFormatVersion: 2 -guid: 288088fdc016525a59f83f1c608e514d -TextureImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 13 - mipmaps: - mipMapMode: 0 - enableMipMap: 1 - sRGBTexture: 1 - linearTexture: 0 - fadeOut: 0 - borderMipMap: 0 - mipMapsPreserveCoverage: 0 - alphaTestReferenceValue: 0.5 - mipMapFadeDistanceStart: 1 - mipMapFadeDistanceEnd: 3 - bumpmap: - convertToNormalMap: 0 - externalNormalMap: 0 - heightScale: 0.25 - normalMapFilter: 0 - flipGreenChannel: 0 - isReadable: 0 - streamingMipmaps: 0 - streamingMipmapsPriority: 0 - vTOnly: 0 - ignoreMipmapLimit: 0 - grayScaleToAlpha: 0 - generateCubemap: 6 - cubemapConvolution: 0 - seamlessCubemap: 0 - textureFormat: 1 - maxTextureSize: 2048 - textureSettings: - serializedVersion: 2 - filterMode: 1 - aniso: 1 - mipBias: 0 - wrapU: 0 - wrapV: 0 - wrapW: 0 - nPOTScale: 1 - lightmap: 0 - compressionQuality: 50 - spriteMode: 0 - spriteExtrude: 1 - spriteMeshType: 1 - alignment: 0 - spritePivot: {x: 0.5, y: 0.5} - spritePixelsToUnits: 100 - spriteBorder: {x: 0, y: 0, z: 0, w: 0} - spriteGenerateFallbackPhysicsShape: 1 - alphaUsage: 1 - alphaIsTransparency: 0 - spriteTessellationDetail: -1 - textureType: 0 - textureShape: 1 - singleChannelComponent: 0 - flipbookRows: 1 - flipbookColumns: 1 - maxTextureSizeSet: 0 - compressionQualitySet: 0 - textureFormatSet: 0 - ignorePngGamma: 0 - applyGammaDecoding: 0 - swizzle: 50462976 - cookieLightType: 0 - platformSettings: - - serializedVersion: 4 - buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 4 - buildTarget: Standalone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - spriteSheet: - serializedVersion: 2 - sprites: [] - outline: [] - customData: - physicsShape: [] - bones: [] - spriteID: - internalID: 0 - vertices: [] - indices: - edges: [] - weights: [] - secondaryTextures: [] - spriteCustomMetadata: - entries: [] - nameFileIdTable: {} - mipmapLimitGroupName: - pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Icons/NeuraalNetwerkIcoonSchets2.png b/NanoBrain/Icons/NeuraalNetwerkIcoonSchets2.png deleted file mode 100644 index 35853d65078c2e6c2326da8a10e5f8d57db43a76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39373 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 diff --git a/NanoBrain/Icons/NeuraalNetwerkIcoonSchets2.png.meta b/NanoBrain/Icons/NeuraalNetwerkIcoonSchets2.png.meta deleted file mode 100644 index 524e4c8..0000000 --- a/NanoBrain/Icons/NeuraalNetwerkIcoonSchets2.png.meta +++ /dev/null @@ -1,117 +0,0 @@ -fileFormatVersion: 2 -guid: e16264b4b7305e5c5b5b1389d6b2f13e -TextureImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 13 - mipmaps: - mipMapMode: 0 - enableMipMap: 1 - sRGBTexture: 1 - linearTexture: 0 - fadeOut: 0 - borderMipMap: 0 - mipMapsPreserveCoverage: 0 - alphaTestReferenceValue: 0.5 - mipMapFadeDistanceStart: 1 - mipMapFadeDistanceEnd: 3 - bumpmap: - convertToNormalMap: 0 - externalNormalMap: 0 - heightScale: 0.25 - normalMapFilter: 0 - flipGreenChannel: 0 - isReadable: 0 - streamingMipmaps: 0 - streamingMipmapsPriority: 0 - vTOnly: 0 - ignoreMipmapLimit: 0 - grayScaleToAlpha: 0 - generateCubemap: 6 - cubemapConvolution: 0 - seamlessCubemap: 0 - textureFormat: 1 - maxTextureSize: 2048 - textureSettings: - serializedVersion: 2 - filterMode: 1 - aniso: 1 - mipBias: 0 - wrapU: 0 - wrapV: 0 - wrapW: 0 - nPOTScale: 1 - lightmap: 0 - compressionQuality: 50 - spriteMode: 0 - spriteExtrude: 1 - spriteMeshType: 1 - alignment: 0 - spritePivot: {x: 0.5, y: 0.5} - spritePixelsToUnits: 100 - spriteBorder: {x: 0, y: 0, z: 0, w: 0} - spriteGenerateFallbackPhysicsShape: 1 - alphaUsage: 1 - alphaIsTransparency: 0 - spriteTessellationDetail: -1 - textureType: 0 - textureShape: 1 - singleChannelComponent: 0 - flipbookRows: 1 - flipbookColumns: 1 - maxTextureSizeSet: 0 - compressionQualitySet: 0 - textureFormatSet: 0 - ignorePngGamma: 0 - applyGammaDecoding: 0 - swizzle: 50462976 - cookieLightType: 0 - platformSettings: - - serializedVersion: 4 - buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 4 - buildTarget: Standalone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - spriteSheet: - serializedVersion: 2 - sprites: [] - outline: [] - customData: - physicsShape: [] - bones: [] - spriteID: - internalID: 0 - vertices: [] - indices: - edges: [] - weights: [] - secondaryTextures: [] - spriteCustomMetadata: - entries: [] - nameFileIdTable: {} - mipmapLimitGroupName: - pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Icons/NeuraalNetwerkIcoonSchets3.png b/NanoBrain/Icons/NeuraalNetwerkIcoonSchets3.png deleted file mode 100644 index f2faf0ceb6c7192aa42dd7536bb0755b7cead3b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40575 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

Qn64b=iQ=0`h)j4_K*TP7AwdnvVO9~Me7l%cQ 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 diff --git a/NanoBrain/Icons/NeuraalNetwerkIcoonSchets3.png.meta b/NanoBrain/Icons/NeuraalNetwerkIcoonSchets3.png.meta deleted file mode 100644 index e7b0a3c..0000000 --- a/NanoBrain/Icons/NeuraalNetwerkIcoonSchets3.png.meta +++ /dev/null @@ -1,117 +0,0 @@ -fileFormatVersion: 2 -guid: 948c13386d926b7bbbca85239a974d85 -TextureImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 13 - mipmaps: - mipMapMode: 0 - enableMipMap: 1 - sRGBTexture: 1 - linearTexture: 0 - fadeOut: 0 - borderMipMap: 0 - mipMapsPreserveCoverage: 0 - alphaTestReferenceValue: 0.5 - mipMapFadeDistanceStart: 1 - mipMapFadeDistanceEnd: 3 - bumpmap: - convertToNormalMap: 0 - externalNormalMap: 0 - heightScale: 0.25 - normalMapFilter: 0 - flipGreenChannel: 0 - isReadable: 0 - streamingMipmaps: 0 - streamingMipmapsPriority: 0 - vTOnly: 0 - ignoreMipmapLimit: 0 - grayScaleToAlpha: 0 - generateCubemap: 6 - cubemapConvolution: 0 - seamlessCubemap: 0 - textureFormat: 1 - maxTextureSize: 2048 - textureSettings: - serializedVersion: 2 - filterMode: 1 - aniso: 1 - mipBias: 0 - wrapU: 0 - wrapV: 0 - wrapW: 0 - nPOTScale: 1 - lightmap: 0 - compressionQuality: 50 - spriteMode: 0 - spriteExtrude: 1 - spriteMeshType: 1 - alignment: 0 - spritePivot: {x: 0.5, y: 0.5} - spritePixelsToUnits: 100 - spriteBorder: {x: 0, y: 0, z: 0, w: 0} - spriteGenerateFallbackPhysicsShape: 1 - alphaUsage: 1 - alphaIsTransparency: 1 - spriteTessellationDetail: -1 - textureType: 0 - textureShape: 1 - singleChannelComponent: 0 - flipbookRows: 1 - flipbookColumns: 1 - maxTextureSizeSet: 0 - compressionQualitySet: 0 - textureFormatSet: 0 - ignorePngGamma: 0 - applyGammaDecoding: 0 - swizzle: 50462976 - cookieLightType: 0 - platformSettings: - - serializedVersion: 4 - buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 4 - buildTarget: Standalone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - spriteSheet: - serializedVersion: 2 - sprites: [] - outline: [] - customData: - physicsShape: [] - bones: [] - spriteID: - internalID: 0 - vertices: [] - indices: - edges: [] - weights: [] - secondaryTextures: [] - spriteCustomMetadata: - entries: [] - nameFileIdTable: {} - mipmapLimitGroupName: - pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Identity.asset b/NanoBrain/Identity.asset deleted file mode 100644 index 2471b04..0000000 --- a/NanoBrain/Identity.asset +++ /dev/null @@ -1,59 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 60a957541c24c57e78018c202ebb1d9b, type: 3} - m_Name: Identity - m_EditorClassIdentifier: Assembly-CSharp::ClusterPrefab - nuclei: - - rid: 2262690531574022216 - references: - version: 2 - RefIds: - - rid: -2 - type: {class: , ns: , asm: } - - rid: 2262690531574022216 - type: {class: Neuron, ns: , asm: Assembly-CSharp} - data: - name: Output - clusterPrefab: {fileID: 11400000} - parent: - rid: -2 - trace: 0 - bias: {x: 0, y: 0, z: 0} - _synapses: [] - combinator: 0 - _curvePreset: 0 - curve: - serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0 - value: 0 - inSlope: 0 - outSlope: 1 - tangentMode: 0 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 1000 - value: 1000 - inSlope: 1 - outSlope: 0 - tangentMode: 0 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 - curveMax: 1 - _receivers: [] diff --git a/NanoBrain/Identity.asset.meta b/NanoBrain/Identity.asset.meta deleted file mode 100644 index b2382a6..0000000 --- a/NanoBrain/Identity.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 5f4d2ea0d0115b3549f8e9aa5e669163 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/LinearAlgebra.meta b/NanoBrain/LinearAlgebra.meta deleted file mode 100644 index c54c1af..0000000 --- a/NanoBrain/LinearAlgebra.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: d98555a675e8e5e879de17db950b55fe -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/LinearAlgebra/.editorconfig b/NanoBrain/LinearAlgebra/.editorconfig deleted file mode 100644 index 1ec7f97..0000000 --- a/NanoBrain/LinearAlgebra/.editorconfig +++ /dev/null @@ -1,19 +0,0 @@ -# EditorConfig is awesome: https://EditorConfig.org - -# top-most EditorConfig file -root = true - -[*] -indent_style = space -indent_size = 4 -end_of_line = crlf -charset = utf-8 -trim_trailing_whitespace = false -insert_final_newline = false -max_line_length = 80 - -[*.cs] -csharp_new_line_before_open_brace = none -# Suppress warnings everywhere -dotnet_diagnostic.IDE1006.severity = none -dotnet_diagnostic.IDE0130.severity = none \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/.gitea/workflows/unit_tests.yaml b/NanoBrain/LinearAlgebra/.gitea/workflows/unit_tests.yaml deleted file mode 100644 index e98b26e..0000000 --- a/NanoBrain/LinearAlgebra/.gitea/workflows/unit_tests.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: Build and Run C# Unit Tests - -on: - push: - branches: - - '**' - pull_request: - branches: - - '**' - -jobs: - build-and-test: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup .NET - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '8.0.x' # Specify the .NET SDK version - - - name: Check Current Directory - run: pwd # Logs the current working directory - - - name: List Files - run: ls -la # Lists all files in the current directory - - - name: Restore Dependencies - run: dotnet restore ./LinearAlgebra-csharp.sln # Restore NuGet packages - - - name: Build the Project - run: dotnet build ./LinearAlgebra-csharp.sln --configuration Release # Build the C# project - - - name: Run Unit Tests - run: dotnet test ./test/LinearAlgebra_Test.csproj --configuration Release # Execute unit tests diff --git a/NanoBrain/LinearAlgebra/.gitignore b/NanoBrain/LinearAlgebra/.gitignore deleted file mode 100644 index b32a30c..0000000 --- a/NanoBrain/LinearAlgebra/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -DoxyGen/DoxyWarnLogfile.txt -.vscode/settings.json -**bin -**obj -**.meta diff --git a/NanoBrain/LinearAlgebra/LinearAlgebra-csharp.sln b/NanoBrain/LinearAlgebra/LinearAlgebra-csharp.sln deleted file mode 100644 index 4b13b2b..0000000 --- a/NanoBrain/LinearAlgebra/LinearAlgebra-csharp.sln +++ /dev/null @@ -1,30 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.2.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinearAlgebra", "src\LinearAlgebra.csproj", "{ECB58727-0354-924D-AE7B-22F6B21097EB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinearAlgebra_Test", "test\LinearAlgebra_Test.csproj", "{715BB399-5FC4-2AC9-3757-177CA0C80774}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {ECB58727-0354-924D-AE7B-22F6B21097EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ECB58727-0354-924D-AE7B-22F6B21097EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ECB58727-0354-924D-AE7B-22F6B21097EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ECB58727-0354-924D-AE7B-22F6B21097EB}.Release|Any CPU.Build.0 = Release|Any CPU - {715BB399-5FC4-2AC9-3757-177CA0C80774}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {715BB399-5FC4-2AC9-3757-177CA0C80774}.Debug|Any CPU.Build.0 = Debug|Any CPU - {715BB399-5FC4-2AC9-3757-177CA0C80774}.Release|Any CPU.ActiveCfg = Release|Any CPU - {715BB399-5FC4-2AC9-3757-177CA0C80774}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E93B8294-87D4-4887-83B7-182A623D5833} - EndGlobalSection -EndGlobal diff --git a/NanoBrain/LinearAlgebra/src/Angle.cs b/NanoBrain/LinearAlgebra/src/Angle.cs deleted file mode 100644 index 7d2fd6b..0000000 --- a/NanoBrain/LinearAlgebra/src/Angle.cs +++ /dev/null @@ -1,341 +0,0 @@ -using System; - -namespace LinearAlgebra { - - public struct AngleFloat { - public const float Rad2Deg = 360.0f / ((float)Math.PI * 2); //0.0174532924F; - public const float Deg2Rad = (float)Math.PI * 2 / 360.0f; //57.29578F; - - private AngleFloat(float degrees) { - this.value = degrees; - } - private readonly float value; - - public static AngleFloat Degrees(float degrees) { - // Reduce it to (-180..180] - if (float.IsFinite(degrees)) { - while (degrees < -180) - degrees += 360; - while (degrees >= 180) - degrees -= 360; - } - return new AngleFloat(degrees); - } - - public static AngleFloat Radians(float radians) { - // Reduce it to (-pi..pi] - if (float.IsFinite(radians)) { - while (radians <= -Math.PI) - radians += 2 * (float)Math.PI; - while (radians > Math.PI) - radians -= 2 * (float)Math.PI; - } - - return new AngleFloat(radians * Rad2Deg); - } - - public static AngleFloat Revolutions(float revolutions) { - // reduce it to (-0.5 .. 0.5] - if (float.IsFinite(revolutions)) { - // Get the integer part - int integerPart = (int)revolutions; - - // Get the decimal part - revolutions -= integerPart; - if (revolutions < -0.5) - revolutions += 1; - if (revolutions >= 0.5) - revolutions -= 1; - } - return new AngleFloat(revolutions * 360); - } - - public readonly float inDegrees => this.value; - - public readonly float inRadians => this.value * Deg2Rad; - - public readonly float inRevolutions => this.value / 360.0f; - - public override string ToString() { - return $"{this.inDegrees}\u00B0"; - } - - public static readonly AngleFloat zero = Degrees(0); - public static readonly AngleFloat deg90 = Degrees(90); - public static readonly AngleFloat deg180 = Degrees(180); - - ///

- /// Get the sign of the angle - /// - /// The angle - /// -1 when the angle is negative, 1 when it is positive and 0 in all other cases - public static int Sign(AngleFloat a) { - if (a.value < 0) - return -1; - if (a.value > 0) - return 1; - return 0; - } - - /// - /// Returns the magnitude of the angle - /// - /// The angle - /// The positive magnitude of the angle - /// Negative values are negated to get a positive result - public static AngleFloat Abs(AngleFloat a) { - if (Sign(a) < 0) - return -a; - else - return a; - } - - /// - /// Tests the equality of two angles - /// - /// - /// - /// True when the angles are equal, false otherwise - /// The equality is determine within the limits of precision of a float - public static bool operator ==(AngleFloat a1, AngleFloat a2) { - return a1.value == a2.value; - } - - /// - /// Tests the inequality of two angles - /// - /// - /// - /// True when the angles are not equal, false otherwise - /// The equality is determine within the limits of precision of a float - public static bool operator !=(AngleFloat a1, AngleFloat a2) { - return a1.value != a2.value; - } - - public override readonly bool Equals(object obj) { - if (obj is AngleFloat other) { - return this == other; - } - return false; - } - - public override readonly int GetHashCode() { - return this.value.GetHashCode(); - } - - - /// - /// Tests if the first angle is greater than the second - /// - /// - /// - /// True when a1 is greater than a2, False otherwise - public static bool operator >(AngleFloat a1, AngleFloat a2) { - return a1.value > a2.value; - } - - /// - /// Tests if the first angle is greater than or equal to the second - /// - /// - /// - /// True when a1 is greater than or equal to a2, False otherwise - public static bool operator >=(AngleFloat a1, AngleFloat a2) { - return a1.value >= a2.value; - } - - /// - /// Tests if the first angle is less than the second - /// - /// - /// - /// True when a1 is less than a2, False otherwise - public static bool operator <(AngleFloat a1, AngleFloat a2) { - return a1.value < a2.value; - } - - /// - /// Tests if the first angle is less than or equal to the second - /// - /// - /// - /// True when a1 is less than or equal to a2, False otherwise - public static bool operator <=(AngleFloat a1, AngleFloat a2) { - return a1.value <= a2.value; - } - - /// - /// Negate the angle - /// - /// The angle - /// The negated angle - /// The negation of -180 is still -180 because the range is (-180..180] - public static AngleFloat operator -(AngleFloat a) { - AngleFloat r = new(-a.value); - return r; - } - - /// - /// Subtract two angles - /// - /// Angle 1 - /// Angle 2 - /// The result of the subtraction - public static AngleFloat operator -(AngleFloat a1, AngleFloat a2) { - AngleFloat r = new(a1.value - a2.value); - return r; - } - /// - /// Add two angles - /// - /// Angle 1 - /// Angle 2 - /// The result of the addition - public static AngleFloat operator +(AngleFloat a1, AngleFloat a2) { - AngleFloat r = new(a1.value + a2.value); - return r; - } - - /// - /// Multiplies the angle - /// - /// The angle to multiply - /// The factor by which the angle is multiplied - /// The multiplied angle - public static AngleFloat operator *(AngleFloat a, float factor) { - return Degrees(a.inDegrees * factor); - } - public static AngleFloat operator *(float factor, AngleFloat a) { - return Degrees(factor * a.inDegrees); - } - - /// - /// Clamp the angle between the given min and max values - /// - /// The angle to clamp - /// The minimum angle - /// The maximum angle - /// The clamped angle - /// Angles are normalized - public static float Clamp(AngleFloat angle, AngleFloat min, AngleFloat max) { - return Float.Clamp(angle.inDegrees, min.inDegrees, max.inDegrees); - } - - /// @brief Calculates the cosine of an angle - /// @param angle The given angle - /// @return The cosine of the angle - public static float Cos(AngleFloat angle) { - return MathF.Cos(angle.inRadians); - } - /// @brief Calculates the sine of an angle - /// @param angle The given angle - /// @return The sine of the angle - public static float Sin(AngleFloat angle) { - return MathF.Sin(angle.inRadians); - } - /// @brief Calculates the tangent of an angle - /// @param angle The given angle - /// @return The tangent of the angle - public static float Tan(AngleFloat angle) { - return MathF.Tan(angle.inRadians); - } - - /// @brief Calculates the arc cosine angle - /// @param f The value - /// @return The arc cosine for the given value - public static AngleFloat Acos(float f) { - return Radians(MathF.Acos(f)); - } - /// @brief Calculates the arc sine angle - /// @param f The value - /// @return The arc sine for the given value - public static AngleFloat Asin(float f) { - return Radians(MathF.Asin(f)); - } - /// @brief Calculates the arc tangent angle - /// @param f The value - /// @return The arc tangent for the given value - public static AngleFloat Atan(float f) { - return Radians(MathF.Atan(f)); - } - /// @brief Calculates the tangent for the given values - /// @param y The vertical value - /// @param x The horizontal value - /// @return The tanget for the given values - /// Uses the y and x signs to compute the quadrant - public static AngleFloat Atan2(float y, float x) { - return Radians(MathF.Atan2(y, x)); - } - - /// - /// Rotate from one angle to the other with a maximum degrees - /// - /// Starting angle - /// Target angle - /// Maximum angle to rotate - /// The resulting angle - /// This function is compatible with radian and degrees angles - public static AngleFloat MoveTowards(AngleFloat fromAngle, AngleFloat toAngle, float maxDegrees) { - maxDegrees = Math.Max(0, maxDegrees); // filter out negative distances - AngleFloat d = toAngle - fromAngle; - float dDegrees = Abs(d).inDegrees; - d = Degrees(Float.Clamp(dDegrees, 0, maxDegrees)); - if (Sign(d) < 0) - d = -d; - return fromAngle + d; - } - } - - - /// - /// %Angle utilities - /// - public static class Angles { - public const float pi = 3.1415927410125732421875F; - // public static float Rad2Deg = 360.0f / ((float)Math.PI * 2); - // public static float Deg2Rad = ((float)Math.PI * 2) / 360.0f; - - - /// - /// Determine the angle difference, result is a normalized angle - /// - /// First first angle - /// The second angle - /// the angle between the two angles - /// Angle values should be degrees - public static float Difference(float a, float b) { - float r = Normalize(b - a); - return r; - } - - /// - /// Normalize an angle to the range -180 < angle <= 180 - /// - /// The angle to normalize - /// The normalized angle in interval (-180..180] - /// Angle values should be in degrees - public static float Normalize(float angle) { - if (float.IsInfinity(angle)) - return angle; - - while (angle <= -180) angle += 360; - while (angle > 180) angle -= 360; - return angle; - } - - /// - /// Map interval of angles between vectors [0..Pi] to interval [0..1] - /// - /// The first vector - /// The second vector - /// The resulting factor in interval [0..1] - /// Vectors a and b must be normalized - /// \deprecated Please use Vector2.ToFactor instead. - // [Obsolete("Please use Vector2.ToFactor instead.")] - // public static float ToFactor(Vector2Float v1, Vector2Float v2) { - // return (1 - Vector2Float.Dot(v1, v2)) / 2; - // } - - } - -} \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/src/Decomposition.cs b/NanoBrain/LinearAlgebra/src/Decomposition.cs deleted file mode 100644 index ddaf434..0000000 --- a/NanoBrain/LinearAlgebra/src/Decomposition.cs +++ /dev/null @@ -1,287 +0,0 @@ -using System; -namespace LinearAlgebra { - class QR { - // QR Decomposition of a matrix A - public static (Matrix2 Q, Matrix2 R) Decomposition(Matrix2 A) { - int nRows = A.nRows; - int nCols = A.nCols; - - float[,] Q = new float[nRows, nCols]; - float[,] R = new float[nCols, nCols]; - - // Perform Gram-Schmidt orthogonalization - for (uint colIx = 0; colIx < nCols; colIx++) { - - // Step 1: v = column(ix) of A - float[] v = new float[nRows]; - for (int rowIx = 0; rowIx < nRows; rowIx++) - v[rowIx] = A.data[rowIx, colIx]; - - // Step 2: Subtract projections of v onto previous q's (orthogonalize) - for (uint colIx2 = 0; colIx2 < colIx; colIx2++) { - float dotProd = 0; - for (int i = 0; i < nRows; i++) - dotProd += Q[i, colIx2] * v[i]; - for (int i = 0; i < nRows; i++) - v[i] -= dotProd * Q[i, colIx2]; - } - - // Step 3: Normalize v to get column(ix) of Q - float norm = 0; - for (int rowIx = 0; rowIx < nRows; rowIx++) - norm += v[rowIx] * v[rowIx]; - norm = (float)Math.Sqrt(norm); - - for (int rowIx = 0; rowIx < nRows; rowIx++) - Q[rowIx, colIx] = v[rowIx] / norm; - - // Store the coefficients of R - for (int colIx2 = 0; colIx2 <= colIx; colIx2++) { - R[colIx2, colIx] = 0; - for (int k = 0; k < nRows; k++) - R[colIx2, colIx] += Q[k, colIx2] * A.data[k, colIx]; - } - } - return (new Matrix2(Q), new Matrix2(R)); - } - - // Reduced QR Decomposition of a matrix A - public static (Matrix2 Q, Matrix2 R) ReducedDecomposition(Matrix2 A) { - int nRows = A.nRows; - int nCols = A.nCols; - - float[,] Q = new float[nRows, nCols]; - float[,] R = new float[nCols, nCols]; - - // Perform Gram-Schmidt orthogonalization - for (int colIx = 0; colIx < nCols; colIx++) { - - // Step 1: v = column(colIx) of A - float[] columnIx = new float[nRows]; - bool isZeroColumn = true; - for (int rowIx = 0; rowIx < nRows; rowIx++) { - columnIx[rowIx] = A.data[rowIx, colIx]; - if (columnIx[rowIx] != 0) - isZeroColumn = false; - } - if (isZeroColumn) { - for (int rowIx = 0; rowIx < nRows; rowIx++) - Q[rowIx, colIx] = 0; - // Set corresponding R element to 0 - R[colIx, colIx] = 0; - - Console.WriteLine($"zero column {colIx}"); - - continue; - } - - // Step 2: Subtract projections of v onto previous q's (orthogonalize) - for (int colIx2 = 0; colIx2 < colIx; colIx2++) { - // Compute the dot product of v and column(colIx2) of Q - float dotProduct = 0; - for (int rowIx2 = 0; rowIx2 < nRows; rowIx2++) - dotProduct += columnIx[rowIx2] * Q[rowIx2, colIx2]; - // Subtract the projection from v - for (int rowIx2 = 0; rowIx2 < nRows; rowIx2++) - columnIx[rowIx2] -= dotProduct * Q[rowIx2, colIx2]; - } - - // Step 3: Normalize v to get column(colIx) of Q - float norm = 0; - for (int rowIx = 0; rowIx < nRows; rowIx++) - norm += columnIx[rowIx] * columnIx[rowIx]; - if (norm == 0) - throw new Exception("invalid value"); - - norm = (float)Math.Sqrt(norm); - - for (int rowIx = 0; rowIx < nRows; rowIx++) - Q[rowIx, colIx] = columnIx[rowIx] / norm; - - // Here is where it deviates from the Full QR Decomposition ! - - // Step 4: Compute the row(colIx) of R - for (int colIx2 = colIx; colIx2 < nCols; colIx2++) { - float dotProduct = 0; - for (int rowIx2 = 0; rowIx2 < nRows; rowIx2++) - dotProduct += Q[rowIx2, colIx] * A.data[rowIx2, colIx2]; - R[colIx, colIx2] = dotProduct; - } - } - if (!float.IsFinite(R[0, 0])) - throw new Exception("invalid value"); - - return (new Matrix2(Q), new Matrix2(R)); - } - } - - class SVD { - // According to ChatGPT, Mathnet uses Golub-Reinsch SVD algorithm - // 1. Bidiagonalization: The input matrix AA is reduced to a bidiagonal form using Golub-Kahan bidiagonalization. - // This process involves applying a sequence of Householder reflections to AA to create a bidiagonal matrix. - // This step reduces the complexity by making the matrix simpler while retaining the essential structure needed for SVD. - // - // 2. Diagonalization: Once the matrix is in bidiagonal form, - // the singular values are computed using an iterative process - // (typically involving QR factorization or Jacobi rotations) until convergence. - // This process diagonalizes the bidiagonal matrix and allows extraction of the singular values. - // - // 3. Computing UU and VTVT: After obtaining the singular values, - // the left singular vectors UU and right singular vectors VTVT are computed - // using the accumulated transformations (such as Householder reflections) from the bidiagonalization step. - - // Bidiagnolizations through Householder transformations - public static (Matrix2 U1, Matrix2 B, Matrix2 V1) Bidiagonalization(Matrix2 A) { - int m = A.nRows; // Rows of A - int n = A.nCols; // Columns of A - float[,] U1 = new float[m, m]; // Left orthogonal matrix - float[,] V1 = new float[n, n]; // Right orthogonal matrix - float[,] B = A.Clone().data; // Copy A to B for transformation - - // Initialize U1 and V1 as identity matrices - for (int i = 0; i < m; i++) - U1[i, i] = 1; - for (int i = 0; i < n; i++) - V1[i, i] = 1; - - // Perform Householder reflections to create a bidiagonal matrix B - for (int j = 0; j < n; j++) { - // Step 1: Construct the Householder vector y - float[] y = new float[m - j]; - for (int i = j; i < m; i++) - y[i - j] = B[i, j]; - - // Step 2: Compute the norm and scalar alpha - float norm = 0; - for (int i = 0; i < y.Length; i++) - norm += y[i] * y[i]; - norm = (float)Math.Sqrt(norm); - - if (B[j, j] > 0) - norm = -norm; - - float alpha = (float)Math.Sqrt(0.5 * (norm * (norm - B[j, j]))); - float r = (float)Math.Sqrt(0.5 * (norm * (norm + B[j, j]))); - - // Step 3: Apply the reflection to zero out below diagonal - for (int k = j; k < n; k++) { - float dot = 0; - for (int i = j; i < m; i++) - dot += y[i - j] * B[i, k]; - dot /= r; - - for (int i = j; i < m; i++) - B[i, k] -= 2 * dot * y[i - j]; - } - - // Step 4: Update U1 with the Householder reflection (U1 * Householder) - for (int i = j; i < m; i++) - U1[i, j] = y[i - j] / alpha; - - // Step 5: Update V1 (storing the Householder vector y) - // Correct indexing: we only need to store part of y in V1 from index j to n - for (int i = j; i < n; i++) - V1[j, i] = B[j, i]; - - // Repeat steps for further columns if necessary - } - return (new Matrix2(U1), new Matrix2(B), new Matrix2(V1)); - } - - public static Matrix2 Bidiagonalize(Matrix2 A) { - int m = A.nRows; // Rows of A - int n = A.nCols; // Columns of A - float[,] B = A.Clone().data; // Copy A to B for transformation - - // Perform Householder reflections to create a bidiagonal matrix B - for (int j = 0; j < n; j++) { - // Step 1: Construct the Householder vector y - float[] y = new float[m - j]; - for (int i = j; i < m; i++) - y[i - j] = B[i, j]; - - // Step 2: Compute the norm and scalar alpha - float norm = 0; - for (int i = 0; i < y.Length; i++) - norm += y[i] * y[i]; - norm = (float)Math.Sqrt(norm); - - if (B[j, j] > 0) - norm = -norm; - - float r = (float)Math.Sqrt(0.5 * (norm * (norm + B[j, j]))); - - // Step 3: Apply the reflection to zero out below diagonal - for (int k = j; k < n; k++) { - float dot = 0; - for (int i = j; i < m; i++) - dot += y[i - j] * B[i, k]; - dot /= r; - - for (int i = j; i < m; i++) - B[i, k] -= 2 * dot * y[i - j]; - } - - // Repeat steps for further columns if necessary - } - return new Matrix2(B); - } - - // QR Iteration for diagonalization of a bidiagonal matrix B - public static (Matrix1 singularValues, Matrix2 U, Matrix2 Vt) QRIteration(Matrix2 B) { - int m = B.nRows; - int n = B.nCols; - - Matrix2 U = new(m, m); // Left singular vectors (U) - Matrix2 Vt = new(n, n); // Right singular vectors (V^T) - float[] singularValues = new float[Math.Min(m, n)]; // Singular values - - // Initialize U and Vt as identity matrices - for (int i = 0; i < m; i++) - U.data[i, i] = 1; - for (int i = 0; i < n; i++) - Vt.data[i, i] = 1; - - // Perform QR iterations - float tolerance = 1e-7f; //1e-12f; for double - bool converged = false; - while (!converged) { - // Perform QR decomposition on the matrix B - (Matrix2 Q, Matrix2 R) = QR.Decomposition(B); - - // Update B to be the product Q * R //R * Q - B = R * Q; - - // Accumulate the transformations in U and Vt - U *= Q; - Vt *= R; - - // Check convergence by looking at the off-diagonal elements of B - converged = true; - for (int i = 0; i < m - 1; i++) { - for (int j = i + 1; j < n; j++) { - if (Math.Abs(B.data[i, j]) > tolerance) { - converged = false; - break; - } - } - } - } - - // Extract singular values (diagonal elements of B) - for (int i = 0; i < Math.Min(m, n); i++) - singularValues[i] = B.data[i, i]; - - return (new Matrix1(singularValues), U, Vt); - } - - public static (Matrix2 U, Matrix1 S, Matrix2 Vt) Decomposition(Matrix2 A) { - if (A.nRows != A.nCols) - throw new ArgumentException("SVD: matrix A has to be square."); - - Matrix2 B = Bidiagonalize(A); - (Matrix1 S, Matrix2 U, Matrix2 Vt) = QRIteration(B); - return (U, S, Vt); - } - } -} \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/src/Direction.cs b/NanoBrain/LinearAlgebra/src/Direction.cs deleted file mode 100644 index fd25b98..0000000 --- a/NanoBrain/LinearAlgebra/src/Direction.cs +++ /dev/null @@ -1,261 +0,0 @@ -using System; -#if UNITY_5_3_OR_NEWER -using Vector3Float = UnityEngine.Vector3; -#endif - -namespace LinearAlgebra -{ - - /// - /// A direction in 3D space - /// - /// A direction is represented using two angles: - /// * The horizontal angle ranging from -180 (inclusive) to 180 (exclusive) - /// degrees which is a rotation in the horizontal plane - /// * A vertical angle ranging from -90 (inclusive) to 90 (exclusive) degrees - /// which is the rotation in the up/down direction applied after the horizontal - /// rotation has been applied. - /// The angles are automatically normalized to stay within the abovenmentioned - /// ranges. - public struct Direction - { - /// @brief horizontal angle, range = (-180..180] degrees - public AngleFloat horizontal; - /// @brief vertical angle, range in degrees = (-90..90] degrees - public AngleFloat vertical; - - /// - /// Create a new direction - /// - /// The horizontal angle - /// The vertical angle - /// The direction will be normalized automatically - /// to ensure the angles are within the allowed ranges - public Direction(AngleFloat horizontal, AngleFloat vertical) - { - this.horizontal = horizontal; - this.vertical = vertical; - this.Normalize(); - } - - /// - /// Create a direction using angle values in degrees - /// - /// The horizontal angle in degrees - /// The vertical angle in degrees - /// The direction - /// The direction will be normalized automatically - /// to ensure the angles are within the allowed ranges - public static Direction Degrees(float horizontal, float vertical) - { - Direction d = new() - { - horizontal = AngleFloat.Degrees(horizontal), - vertical = AngleFloat.Degrees(vertical) - }; - d.Normalize(); - return d; - } - /// - /// Create a direction using angle values in radians - /// - /// The horizontal angle in radians - /// The vertical angle in radians - /// The direction - public static Direction Radians(float horizontal, float vertical) - { - Direction d = new() - { - horizontal = AngleFloat.Radians(horizontal), - vertical = AngleFloat.Radians(vertical) - }; - d.Normalize(); - return d; - } - - public override readonly string ToString() { - return $"Direction(h: {this.horizontal}, v: {this.vertical})"; - } - - /// - /// A forward direction with zero for both angles - /// - public readonly static Direction forward = Degrees(0, 0); - /// - /// A backward direction with horizontal angle -180 and zero vertical - /// angle - /// - public readonly static Direction backward = Degrees(-180, 0); - /// - /// A upward direction with zero horizontal angle and vertical angle 90 - /// - public readonly static Direction up = Degrees(0, 90); - /// - /// A downward direction with zero horizontal angle and vertical angle - /// -90 - /// - public readonly static Direction down = Degrees(0, -90); - /// - /// A left-pointing direction with horizontal angle -90 and zero - /// vertical angle - /// - public readonly static Direction left = Degrees(-90, 0); - /// - /// A right-pointing direction with horizontal angle 90 and zero - /// vertical angle - /// - public readonly static Direction right = Degrees(90, 0); - - private void Normalize() - { - if (this.vertical > AngleFloat.deg90 || this.vertical < -AngleFloat.deg90) - { - this.horizontal += AngleFloat.deg180; - this.vertical = AngleFloat.Degrees(180 - this.vertical.inDegrees); - } - } - -#if UNITY_5_3_OR_NEWER - /// - /// Convert the direction into a carthesian vector - /// - /// The carthesian vector corresponding to this direction. - public readonly UnityEngine.Vector3 ToVector3() { - // Convert degrees to radians - float radH = this.horizontal.inRadians; - float radV = this.vertical.inRadians; - - // Calculate Vector - float cosV = MathF.Cos(radV); - 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); - } - - /// - /// Convert a carthesian vector into a direction - /// - /// The carthesian vector - /// The direction - /// Information about the length of the carthesian vector is not - /// included in this transformation - public static Direction FromVector3(UnityEngine.Vector3 v) { - AngleFloat horizontal = AngleFloat.Atan2(v.x, v.z); - AngleFloat vertical = AngleFloat.deg90 - AngleFloat.Acos(v.y); - Direction d = new(horizontal, vertical); - return d; - } -#else - /// - /// Convert the direction into a carthesian vector - /// - /// The carthesian vector corresponding to this direction. - public readonly Vector3Float ToVector3() { - // Quaternion q = Quaternion.Euler(90 - this.vertical.inDegrees, this.horizontal.inDegrees, 0); - // Vector3Float v = q * Vector3Float.forward; - // return v; - - float radH = this.horizontal.inRadians; - float radV = this.vertical.inRadians; - - float cosV = MathF.Cos(radV); - float sinV = MathF.Sin(radV); - - float horizontal = cosV * MathF.Sin(radH); - float vertical = sinV; - float depth = cosV * MathF.Cos(radH); - - return new Vector3Float(horizontal, vertical, depth); - } - - /// - /// Convert a carthesian vector into a direction - /// - /// The carthesian vector - /// The direction - /// Information about the length of the carthesian vector is not - /// included in this transformation - 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); - return d; - } -#endif - - public static Direction operator -(Direction d) { - AngleFloat horizontal = d.horizontal + AngleFloat.deg180; - AngleFloat vertical = -d.vertical; - return new Direction(horizontal, vertical); - } - - /// - /// Tests the equality of two directions - /// - /// - /// - /// True when the direction angles are equal, false otherwise. - public static bool operator ==(Direction d1, Direction d2) - { - bool horizontalEq = d1.horizontal == d2.horizontal; - bool verticalEq = d1.vertical == d2.vertical; - return horizontalEq && verticalEq; - } - - /// - /// Tests the inequality of two directions - /// - /// - /// - /// True when the direction angles are not equal, false otherwise. - 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) - { - if (obj is not Direction d) - return false; - - bool horizontalEq = this.horizontal == d.horizontal; - bool verticalEq = this.vertical == d.vertical; - return horizontalEq && verticalEq; - } - - - public override readonly int GetHashCode() - { - return HashCode.Combine(horizontal, vertical); - } - - public static AngleFloat UnsignedAngle(Direction d1, Direction d2) { - // Convert angles from degrees to radians - float horizontal1Rad = d1.horizontal.inRadians; - float vertical1Rad = d1.vertical.inRadians; - - float horizontal2Rad = d2.horizontal.inRadians; - float vertical2Rad = d2.vertical.inRadians; - - // Calculate the cosine of the angle using the spherical law of cosines - float cosTheta = MathF.Sin(vertical1Rad) * MathF.Sin(vertical2Rad) + - MathF.Cos(vertical1Rad) * MathF.Cos(vertical2Rad) * - MathF.Cos(horizontal1Rad - horizontal2Rad); - - // Clip cosTheta to the valid range for acos - cosTheta = Float.Clamp(cosTheta, -1.0f, 1.0f); - - // Calculate the angle - AngleFloat angle = AngleFloat.Acos(cosTheta); - return angle; - } - } - -} \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/src/Float.cs b/NanoBrain/LinearAlgebra/src/Float.cs deleted file mode 100644 index 2217b84..0000000 --- a/NanoBrain/LinearAlgebra/src/Float.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace LinearAlgebra { - - /// - /// Float number utilities - /// - public class Float { - /// - /// The precision of float numbers - /// - public const float epsilon = 1E-05f; - /// - /// The square of the float number precision - /// - public const float sqrEpsilon = 1e-10f; - - /// - /// Clamp the value between the given minimum and maximum values - /// - /// The value to clamp - /// The minimum value - /// The maximum value - /// The clamped value - public static float Clamp(float f, float min, float max) { - if (f < min) - return min; - if (f > max) - return max; - return f; - } - - /// - /// Clamp the value between to the interval [0..1] - /// - /// The value to clamp - /// The clamped value - public static float Clamp01(float f) { - return Clamp(f, 0, 1); - } - } - -} \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/src/LinearAlgebra.csproj b/NanoBrain/LinearAlgebra/src/LinearAlgebra.csproj deleted file mode 100644 index d2d5a85..0000000 --- a/NanoBrain/LinearAlgebra/src/LinearAlgebra.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - false - false - net8.0 - - - - - - - - diff --git a/NanoBrain/LinearAlgebra/src/Matrix.cs b/NanoBrain/LinearAlgebra/src/Matrix.cs deleted file mode 100644 index 37c6c24..0000000 --- a/NanoBrain/LinearAlgebra/src/Matrix.cs +++ /dev/null @@ -1,689 +0,0 @@ -using System; -#if UNITY_5_3_OR_NEWER -using Vector3Float = UnityEngine.Vector3; -using Vector2Float = UnityEngine.Vector2; -using Quaternion = UnityEngine.Quaternion; -#endif - -namespace LinearAlgebra { - - public readonly struct Slice - { - public int start { get; } - public int stop { get; } - public Slice(int start, int stop) - { - this.start = start; - this.stop = stop; - } - } - - public class Matrix2 { - public float[,] data { get; } - - public int nRows => data.GetLength(0); - public int nCols => data.GetLength(1); - - public Matrix2(int nRows, int nCols) - { - this.data = new float[nRows, nCols]; - } - public Matrix2(float[,] data) { - this.data = data; - } - - public Matrix2 Clone() { - float[,] data = new float[this.nRows, nCols]; - for (int rowIx = 0; rowIx < this.nRows; rowIx++) { - for (int colIx = 0; colIx < this.nCols; colIx++) - data[rowIx, colIx] = this.data[rowIx, colIx]; - } - return new Matrix2(data); - } - - public static Matrix2 Zero(int nRows, int nCols) - { - return new Matrix2(nRows, nCols); - } - - public static Matrix2 FromVector3(Vector3Float v) { - float[,] result = new float[3, 1]; - result[0, 0] = v.horizontal; - result[1, 0] = v.vertical; - result[2, 0] = v.depth; - return new Matrix2(result); - } - - public static Matrix2 Identity(int size) - { - return Diagonal(1, size); - } - public static Matrix2 Identity(int nRows, int nCols) - { - Matrix2 m = Zero(nRows, nCols); - m.FillDiagonal(1); - return m; - } - - public static Matrix2 Diagonal(Matrix1 v) { - float[,] resultData = new float[v.size, v.size]; - for (int ix = 0; ix < v.size; ix++) - resultData[ix, ix] = v.data[ix]; - return new Matrix2(resultData); - } - public static Matrix2 Diagonal(float f, int size) - { - float[,] resultData = new float[size, size]; - for (int ix = 0; ix < size; ix++) - resultData[ix, ix] = f; - return new Matrix2(resultData); - } - public void FillDiagonal(Matrix1 v) - { - int n = (int)Math.Min(Math.Min(this.nRows, this.nCols), v.size); - for (int ix = 0; ix < n; ix++) - this.data[ix, ix] = v.data[ix]; - } - public void FillDiagonal(float f) - { - int n = Math.Min(this.nRows, this.nCols); - for (int ix = 0; ix < n; ix++) - this.data[ix, ix] = f; - } - - public static Matrix2 SkewMatrix(Vector3Float v) { - float[,] result = new float[3, 3] { - {0, -v.depth, v.vertical}, - {v.depth, 0, -v.horizontal}, - {-v.vertical, v.horizontal, 0} - }; - return new Matrix2(result); - } - - public Matrix1 GetRow(int rowIx) { - float[] row = new float[this.nCols]; - for (int colIx = 0; colIx < this.nCols; colIx++) { - row[colIx] = this.data[rowIx, colIx]; - } - return new Matrix1(row); - } - -#if UNITY_5_3_OR_NEWER - public UnityEngine.Vector3 GetRow3(int rowIx) { - int cols = this.nCols; - UnityEngine.Vector3 row = new() { - x = this.data[rowIx, 0], - y = this.data[rowIx, 1], - z = this.data[rowIx, 2] - }; - return row; - } -#endif - public void SetRow(int rowIx, Matrix1 v) { - for (uint ix = 0; ix < v.size; ix++) - this.data[rowIx, ix] = v.data[ix]; - } - public void SetRow3(int rowIx, Vector3Float v) { - this.data[rowIx, 0] = v.horizontal; - this.data[rowIx, 1] = v.vertical; - this.data[rowIx, 2] = v.depth; - } - - public void SwapRows(int row1, int row2) { - for (uint ix = 0; ix < this.nCols; ix++) { - float temp = this.data[row1, ix]; - this.data[row1, ix] = this.data[row2, ix]; - this.data[row2, ix] = temp; - } - } - - public Matrix1 GetColumn(int colIx) - { - float[] column = new float[this.nRows]; - for (int i = 0; i < this.nRows; i++) { - column[i] = this.data[i, colIx]; - } - return new Matrix1(column); - } - public void SetColumn(int colIx, Matrix1 v) { - for (uint ix = 0; ix < v.size; ix++) - this.data[ix, colIx] = v.data[ix]; - } - public void SetColumn(int colIx, Vector3Float v) { - this.data[0, colIx] = v.horizontal; - this.data[1, colIx] = v.vertical; - this.data[2, colIx] = v.depth; - } - - public static bool AllClose(Matrix2 A, Matrix2 B, float atol = 1e-08f) { - for (int i = 0; i < A.nRows; i++) { - for (int j = 0; j < A.nCols; j++) { - float d = MathF.Abs(A.data[i, j] - B.data[i, j]); - if (d > atol) - return false; - } - } - return true; - } - - public Matrix2 Transpose() { - float[,] resultData = new float[this.nCols, this.nRows]; - for (uint rowIx = 0; rowIx < this.nRows; rowIx++) { - for (uint colIx = 0; colIx < this.nCols; colIx++) - resultData[colIx, rowIx] = this.data[rowIx, colIx]; - } - return new Matrix2(resultData); - // double checked code - } - public Matrix2 transposed { - get => Transpose(); - } - - public static Matrix2 operator -(Matrix2 m) { - float[,] result = new float[m.nRows, m.nCols]; - - for (int i = 0; i < m.nRows; i++) { - for (int j = 0; j < m.nCols; j++) - result[i, j] = -m.data[i, j]; - } - return new Matrix2(result); - } - - public static Matrix2 operator -(Matrix2 A, Matrix2 B) { - if (A.nRows != B.nRows || A.nCols != B.nCols) - throw new System.ArgumentException("Size of A must match size of B."); - - float[,] result = new float[A.nRows, B.nCols]; - - for (int i = 0; i < A.nRows; i++) { - for (int j = 0; j < A.nCols; j++) - result[i, j] = A.data[i, j] - B.data[i, j]; - } - return new Matrix2(result); - } - - public static Matrix2 operator +(Matrix2 A, Matrix2 B) { - if (A.nRows != B.nRows || A.nCols != B.nCols) - throw new System.ArgumentException("Size of A must match size of B."); - - float[,] result = new float[A.nRows, B.nCols]; - - for (int i = 0; i < A.nRows; i++) { - for (int j = 0; j < A.nCols; j++) - result[i, j] = A.data[i, j] + B.data[i, j]; - } - return new Matrix2(result); - } - - public static Matrix2 operator *(Matrix2 A, Matrix2 B) { - if (A.nCols != B.nRows) - throw new System.ArgumentException("Number of columns in A must match number of rows in B."); - - float[,] result = new float[A.nRows, B.nCols]; - - for (int i = 0; i < A.nRows; i++) { - for (int j = 0; j < B.nCols; j++) { - float sum = 0.0f; - for (int k = 0; k < A.nCols; k++) - sum += A.data[i, k] * B.data[k, j]; - - result[i, j] = sum; - } - } - - return new Matrix2(result); - // double checked code - } - - public static Matrix1 operator *(Matrix2 A, Matrix1 v) { - float[] result = new float[A.nRows]; - - for (int i = 0; i < A.nRows; i++) { - for (int j = 0; j < A.nCols; j++) { - result[i] += A.data[i, j] * v.data[j]; - } - } - - return new Matrix1(result); - } - - public static Vector3Float operator *(Matrix2 A, Vector3Float v) { - return new Vector3Float( - A.data[0, 0] * v.horizontal + A.data[0, 1] * v.vertical + A.data[0, 2] * v.depth, - A.data[1, 0] * v.horizontal + A.data[1, 1] * v.vertical + A.data[1, 2] * v.depth, - A.data[2, 0] * v.horizontal + A.data[2, 1] * v.vertical + A.data[2, 2] * v.depth - ); - } - - public static Matrix2 operator *(Matrix2 A, float s) { - float[,] result = new float[A.nRows, A.nCols]; - - for (int i = 0; i < A.nRows; i++) { - for (int j = 0; j < A.nCols; j++) - result[i, j] = A.data[i, j] * s; - } - - return new Matrix2(result); - } - public static Matrix2 operator *(float s, Matrix2 A) { - return A * s; - } - - public static Matrix2 operator /(Matrix2 A, float s) { - float[,] result = new float[A.nRows, A.nCols]; - - for (int i = 0; i < A.nRows; i++) { - for (int j = 0; j < A.nCols; j++) - result[i, j] = A.data[i, j] / s; - } - - return new Matrix2(result); - } - public static Matrix2 operator /(float s, Matrix2 A) { - float[,] result = new float[A.nRows, A.nCols]; - - for (int i = 0; i < A.nRows; i++) { - for (int j = 0; j < A.nCols; j++) - result[i, j] = s / A.data[i, j]; - } - - return new Matrix2(result); - } - - public Matrix2 GetRows(Slice slice) { - return GetRows(slice.start, slice.stop); - } - public Matrix2 GetRows(int from, int to) { - if (from < 0 || to >= this.nRows) - throw new System.ArgumentException("Slice index out of range."); - - float[,] result = new float[to - from, this.nCols]; - int resultRowIx = 0; - for (int rowIx = from; rowIx < to; rowIx++) { - for (int colIx = 0; colIx < this.nCols; colIx++) - result[resultRowIx, colIx] = this.data[rowIx, colIx]; - - resultRowIx++; - } - - return new Matrix2(result); - } - - public Matrix2 Slice(Slice slice) - { - return Slice(slice.start, slice.stop); - } - public Matrix2 Slice(int from, int to) - { - if (from < 0 || to >= this.nRows) - throw new System.ArgumentException("Slice index out of range."); - - float[,] result = new float[to - from, this.nCols]; - int resultRowIx = 0; - for (int rowIx = from; rowIx < to; rowIx++) - { - for (int colIx = 0; colIx < this.nCols; colIx++) - { - result[resultRowIx, colIx] = this.data[rowIx, colIx]; - } - resultRowIx++; - } - - return new Matrix2(result); - } - public Matrix2 Slice(Slice rowRange, Slice colRange) { - return Slice((rowRange.start, rowRange.stop), (colRange.start, colRange.stop)); - } - public Matrix2 Slice((int start, int stop) rowRange, (int start, int stop) colRange) - { - float[,] result = new float[rowRange.stop - rowRange.start, colRange.stop - colRange.start]; - - int resultRowIx = 0; - int resultColIx = 0; - for (int i = rowRange.start; i < rowRange.stop; i++) - { - for (int j = colRange.start; j < colRange.stop; j++) - result[resultRowIx, resultColIx] = this.data[i, j]; - } - return new Matrix2(result); - } - - public void UpdateSlice(Slice slice, Matrix2 m) { - UpdateSlice((slice.start, slice.stop), m); - } - public void UpdateSlice((int start, int stop) slice, Matrix2 m) { - // if (slice.start == slice.stop) - // Console.WriteLine("WARNING: no data is updates when start equals stop in a slice!"); - int mRowIx = 0; - for (int rowIx = slice.start; rowIx < slice.stop; rowIx++, mRowIx++) { - for (int colIx = 0; colIx < this.nCols; colIx++) - this.data[rowIx, colIx] = m.data[mRowIx, colIx]; - } - } - - public void UpdateSlice(Slice rowRange, Slice colRange, Matrix2 m) - { - UpdateSlice((rowRange.start, rowRange.stop), (colRange.start, colRange.stop), m); - } - public void UpdateSlice((int start, int stop) rowRange, (int start, int stop) colRange, Matrix2 m) - { - for (int i = rowRange.start; i < rowRange.stop; i++) - { - for (int j = colRange.start; j < colRange.stop; j++) - this.data[i, j] = m.data[i - rowRange.start, j - colRange.start]; - } - } - - public Matrix2 Inverse() { - Matrix2 A = this; - // unchecked - int n = A.nRows; - - // Create an identity matrix of the same size as the original matrix - float[,] augmentedMatrix = new float[n, 2 * n]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - augmentedMatrix[i, j] = A.data[i, j]; - augmentedMatrix[i, j + n] = (i == j) ? 1 : 0; // Identity matrix - } - } - - // Perform Gaussian elimination - for (int i = 0; i < n; i++) { - // Find the pivot row - float pivot = augmentedMatrix[i, i]; - if (Math.Abs(pivot) < 1e-10) // Check for singular matrix - throw new InvalidOperationException("Matrix is singular and cannot be inverted."); - - // Normalize the pivot row - for (int j = 0; j < 2 * n; j++) - augmentedMatrix[i, j] /= pivot; - - // Eliminate the column below the pivot - for (int j = i + 1; j < n; j++) { - float factor = augmentedMatrix[j, i]; - for (int k = 0; k < 2 * n; k++) - augmentedMatrix[j, k] -= factor * augmentedMatrix[i, k]; - } - } - - // Back substitution - for (int i = n - 1; i >= 0; i--) - { - // Eliminate the column above the pivot - for (int j = i - 1; j >= 0; j--) - { - float factor = augmentedMatrix[j, i]; - for (int k = 0; k < 2 * n; k++) - augmentedMatrix[j, k] -= factor * augmentedMatrix[i, k]; - } - } - - // Extract the inverse matrix from the augmented matrix - float[,] inverse = new float[n, n]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) - inverse[i, j] = augmentedMatrix[i, j + n]; - } - - return new Matrix2(inverse); - } - - public float Determinant() - { - int n = this.nRows; - if (n != this.nCols) - throw new System.ArgumentException("Matrix must be square."); - - if (n == 1) - return this.data[0, 0]; // Base case for 1x1 matrix - - if (n == 2) // Base case for 2x2 matrix - return this.data[0, 0] * this.data[1, 1] - this.data[0, 1] * this.data[1, 0]; - - float det = 0; - for (int col = 0; col < n; col++) - det += (col % 2 == 0 ? 1 : -1) * this.data[0, col] * this.Minor(0, col).Determinant(); - - return det; - } - - // Helper function to compute the minor of a matrix - private Matrix2 Minor(int rowToRemove, int colToRemove) - { - int n = this.nRows; - float[,] minor = new float[n - 1, n - 1]; - - int r = 0, c = 0; - for (int i = 0; i < n; i++) { - if (i == rowToRemove) continue; - - c = 0; - for (int j = 0; j < n; j++) { - if (j == colToRemove) continue; - - minor[r, c] = this.data[i, j]; - c++; - } - r++; - } - - return new Matrix2(minor); - } - - public static Matrix2 DeleteRows(Matrix2 A, Slice rowRange) { - float[,] result = new float[A.nRows - (rowRange.stop - rowRange.start), A.nCols]; - - int resultRowIx = 0; - for (int i = 0; i < A.nRows; i++) { - if (i >= rowRange.start && i < rowRange.stop) - continue; - - for (int j = 0; j < A.nCols; j++) - result[resultRowIx, j] = A.data[i, j]; - - resultRowIx++; - } - return new Matrix2(result); - } - - internal static Matrix2 DeleteColumns(Matrix2 A, Slice colRange) { - float[,] result = new float[A.nRows, A.nCols - (colRange.stop - colRange.start)]; - - for (int i = 0; i < A.nRows; i++) { - int resultColIx = 0; - for (int j = 0; j < A.nCols; j++) { - if (j >= colRange.start && j < colRange.stop) - continue; - - result[i, resultColIx++] = A.data[i, j]; - } - } - return new Matrix2(result); - } - } - - public class Matrix1 - { - public float[] data { get; } - - public int size => data.GetLength(0); - - public Matrix1(int size) - { - this.data = new float[size]; - } - - public Matrix1(float[] data) { - this.data = data; - } - - public static Matrix1 Zero(int size) - { - return new Matrix1(size); - } - - public static Matrix1 FromVector2(Vector2Float v) { - float[] result = new float[2]; - result[0] = v.horizontal; - result[1] = v.vertical; - return new Matrix1(result); - } - - public static Matrix1 FromVector3(Vector3Float v) { - float[] result = new float[3]; - result[0] = v.horizontal; - result[1] = v.vertical; - result[2] = v.depth; - return new Matrix1(result); - } - -#if UNITY_5_3_OR_NEWER - public static Matrix1 FromQuaternion(Quaternion q) { - float[] result = new float[4]; - result[0] = q.x; - result[1] = q.y; - result[2] = q.z; - result[3] = q.w; - return new Matrix1(result); - } -#endif - - public Vector2Float vector2 { - get { - if (this.size != 2) - throw new System.ArgumentException("Matrix1 must be of size 2"); - return new Vector2Float(this.data[0], this.data[1]); - } - } - public Vector3Float vector3 { - get { - if (this.size != 3) - throw new System.ArgumentException("Matrix1 must be of size 3"); - return new Vector3Float(this.data[0], this.data[1], this.data[2]); - } - } - -#if UNITY_5_3_OR_NEWER - public Quaternion quaternion { - get { - if (this.size != 4) - throw new System.ArgumentException("Matrix1 must be of size 4"); - return new Quaternion(this.data[0], this.data[1], this.data[2], this.data[3]); - } - } -#endif - - public Matrix1 Clone() { - float[] data = new float[this.size]; - for (int rowIx = 0; rowIx < this.size; rowIx++) - data[rowIx] = this.data[rowIx]; - return new Matrix1(data); - } - - - public float magnitude { - get { - float sum = 0; - foreach (var elm in data) - sum += elm; - return sum / data.Length; - } - } - public static Matrix1 operator +(Matrix1 A, Matrix1 B) { - if (A.size != B.size) - throw new System.ArgumentException("Size of A must match size of B."); - - float[] result = new float[A.size]; - - for (int i = 0; i < A.size; i++) { - result[i] = A.data[i] + B.data[i]; - } - return new Matrix1(result); - } - - public Matrix2 Transpose() { - float[,] r = new float[1, this.size]; - for (uint colIx = 0; colIx < this.size; colIx++) - r[1, colIx] = this.data[colIx]; - - return new Matrix2(r); - } - - public static float Dot(Matrix1 a, Matrix1 b) { - if (a.size != b.size) - throw new System.ArgumentException("Vectors must be of the same length."); - - float result = 0.0f; - for (int i = 0; i < a.size; i++) { - result += a.data[i] * b.data[i]; - } - return result; - } - - public static Matrix1 operator -(Matrix1 A, Matrix1 B) { - if (A.size != B.size) - throw new System.ArgumentException("Size of A must match size of B."); - - float[] result = new float[A.size]; - - for (int i = 0; i < A.size; i++) { - result[i] = A.data[i] - B.data[i]; - } - return new Matrix1(result); - } - - public static Matrix1 operator *(Matrix1 A, float f) - { - float[] result = new float[A.size]; - - for (int i = 0; i < A.size; i++) - result[i] += A.data[i] * f; - - return new Matrix1(result); - } - public static Matrix1 operator *(float f, Matrix1 A) { - return A * f; - } - - public static Matrix1 operator /(Matrix1 A, float f) { - float[] result = new float[A.size]; - - for (int i = 0; i < A.size; i++) - result[i] = A.data[i] / f; - - return new Matrix1(result); - } - public static Matrix1 operator /(float f, Matrix1 A) { - float[] result = new float[A.size]; - - for (int i = 0; i < A.size; i++) - result[i] = f / A.data[i]; - - return new Matrix1(result); - } - - public Matrix1 Slice(Slice range) - { - return Slice(range.start, range.stop); - } - public Matrix1 Slice(int from, int to) - { - if (from < 0 || to >= this.size) - throw new System.ArgumentException("Slice index out of range."); - - float[] result = new float[to - from]; - int resultIx = 0; - for (int ix = from; ix < to; ix++) - result[resultIx++] = this.data[ix]; - - return new Matrix1(result); - } - public void UpdateSlice(Slice slice, Matrix1 v) { - int vIx = 0; - for (int ix = slice.start; ix < slice.stop; ix++, vIx++) - this.data[ix] = v.data[vIx]; - } - } - -} \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/src/Quat32.cs b/NanoBrain/LinearAlgebra/src/Quat32.cs deleted file mode 100644 index 19ee9bc..0000000 --- a/NanoBrain/LinearAlgebra/src/Quat32.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; - -namespace LinearAlgebra { - public class Quat32 { - public float x; - public float y; - 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; - this.y = y; - this.z = z; - this.w = w; - } - - public static Quat32 FromSwingTwist(SwingTwist s) { - Quat32 q32 = Quat32.Euler(-s.swing.vertical.inDegrees, s.swing.horizontal.inDegrees, s.twist.inDegrees); - return q32; - } - - public static Quat32 Euler(float yaw, float pitch, float roll) { - float rollOver2 = roll * AngleFloat.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 Quat32() { - 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) * AngleFloat.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) * AngleFloat.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) * AngleFloat.Rad2Deg; - up = (float)Math.Atan2(2 * this.y * this.w - 2 * this.x * this.z, 1 - 2 * sqy - 2 * sqz) * AngleFloat.Rad2Deg; - forward = (float)Math.Asin(2 * test) * AngleFloat.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/NanoBrain/LinearAlgebra/src/Quaternion.cs b/NanoBrain/LinearAlgebra/src/Quaternion.cs deleted file mode 100644 index 7936843..0000000 --- a/NanoBrain/LinearAlgebra/src/Quaternion.cs +++ /dev/null @@ -1,582 +0,0 @@ -using System; -#if UNITY_5_3_OR_NEWER -using Quaternion = UnityEngine.Quaternion; -#endif - -namespace LinearAlgebra { - -#if UNITY_5_3_OR_NEWER - public class QuaternionExtensions { - public static Quaternion Reflect(Quaternion q) { - return new(-q.x, -q.y, -q.z, q.w); - } - - public static Matrix2 ToRotationMatrix(Quaternion q) { - float w = q.x, x = q.y, y = q.z, z = q.w; - - float[,] result = new float[,] - { - { 1 - 2 * (y * y + z * z), 2 * (x * y - w * z), 2 * (x * z + w * y) }, - { 2 * (x * y + w * z), 1 - 2 * (x * x + z * z), 2 * (y * z - w * x) }, - { 2 * (x * z - w * y), 2 * (y * z + w * x), 1 - 2 * (x * x + y * y) } - }; - return new Matrix2(result); - } - - public static Quaternion FromRotationMatrix(Matrix2 m) { - float trace = m.data[0, 0] + m.data[1, 1] + m.data[2, 2]; - float w, x, y, z; - - if (trace > 0) { - float s = 0.5f / (float)Math.Sqrt(trace + 1.0f); - w = 0.25f / s; - x = (m.data[2, 1] - m.data[1, 2]) * s; - y = (m.data[0, 2] - m.data[2, 0]) * s; - z = (m.data[1, 0] - m.data[0, 1]) * s; - } - else { - if (m.data[0, 0] > m.data[1, 1] && m.data[0, 0] > m.data[2, 2]) { - float s = 2.0f * (float)Math.Sqrt(1.0f + m.data[0, 0] - m.data[1, 1] - m.data[2, 2]); - w = (m.data[2, 1] - m.data[1, 2]) / s; - x = 0.25f * s; - y = (m.data[0, 1] + m.data[1, 0]) / s; - z = (m.data[0, 2] + m.data[2, 0]) / s; - } - else if (m.data[1, 1] > m.data[2, 2]) { - float s = 2.0f * (float)Math.Sqrt(1.0f + m.data[1, 1] - m.data[0, 0] - m.data[2, 2]); - w = (m.data[0, 2] - m.data[2, 0]) / s; - x = (m.data[0, 1] + m.data[1, 0]) / s; - y = 0.25f * s; - z = (m.data[1, 2] + m.data[2, 1]) / s; - } - else { - float s = 2.0f * (float)Math.Sqrt(1.0f + m.data[2, 2] - m.data[0, 0] - m.data[1, 1]); - w = (m.data[1, 0] - m.data[0, 1]) / s; - x = (m.data[0, 2] + m.data[2, 0]) / s; - y = (m.data[1, 2] + m.data[2, 1]) / s; - z = 0.25f * s; - } - } - - return new Quaternion(x, y, z, w); - } - } -#else - public struct Quaternion { - public float x; - public float y; - public float z; - public float w; - - /// - /// create a new quaternion with the given values - /// - /// x component - /// y component - /// z component - /// w component - public Quaternion(float x, float y, float z, float w) { - this.x = x; - this.y = y; - this.z = z; - this.w = w; - } - - /// - /// An identity quaternion - /// - public static readonly Quaternion identity = new(0, 0, 0, 1); - - private readonly Vector3Float xyz => new(x, y, z); - - private readonly float magnitude => MathF.Sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); - - private readonly float sqrMagnitude => x * x + y * y + z * z + w * w; - - /// - /// Convert to unit quaternion - /// - /// This will preserve the orientation, - /// but ensures that it is a unit quaternion. - public readonly Quaternion normalized { - get { - float length = this.magnitude; - Quaternion q = new(this.x / length, this.y / length, this.z / length, this.w / length); - return q; - } - } - - /// - /// Convert to unity quaternion - /// - /// The quaternion to convert - /// A unit quaternion - /// This will preserve the orientation, - /// but ensures that it is a unit quaternion. - public static Quaternion Normalize(Quaternion q) { - return q.normalized; - } - - /// - /// Convert to euler angles - /// - /// The quaternion to convert - /// A vector containing euler angles - /// The euler angles performed in the order: Z, X, Y - public static Vector3Float ToAngles(Quaternion q) { - // Extract Euler angles in Unity order (X = pitch, Y = yaw, Z = roll), returned in degrees as (x,pitch),(y,yaw),(z,roll) - // Handle singularities/gimbal lock - float test = 2f * (q.w * q.x - q.y * q.z); - // clamp - if (test >= 1f) test = 1f; - if (test <= -1f) test = -1f; - - float pitch = MathF.Asin(test); // X - - float roll = MathF.Atan2(2f * (q.w * q.z + q.x * q.y), - 1f - 2f * (q.x * q.x + q.z * q.z)); // Z - - float yaw = MathF.Atan2(2f * (q.w * q.y + q.x * q.z), - 1f - 2f * (q.y * q.y + q.x * q.x)); // Y - - const float rad2deg = 180f / MathF.PI; - return new Vector3Float(pitch * rad2deg, yaw * rad2deg, roll * rad2deg); - // float test = q.x * q.y + q.z * q.w; - // if (test > 0.499f) // singularity at north pole - // return new Vector3Float(0, 2 * MathF.Atan2(q.x, q.w) * AngleFloat.Rad2Deg, 90); - - // else if (test < -0.499f) // singularity at south pole - // return new Vector3Float(0, -2 * MathF.Atan2(q.x, q.w) * AngleFloat.Rad2Deg, -90); - - // else { - // float sqx = q.x * q.x; - // float sqy = q.y * q.y; - // float sqz = q.z * q.z; - - // return new Vector3Float( - // MathF.Atan2(2 * q.x * q.w - 2 * q.y * q.z, 1 - 2 * sqx - 2 * sqz) * - // AngleFloat.Rad2Deg, - // MathF.Atan2(2 * q.y * q.w - 2 * q.x * q.z, 1 - 2 * sqy - 2 * sqz) * - // AngleFloat.Rad2Deg, - // MathF.Asin(2 * test) * AngleFloat.Rad2Deg); - // } - } - - /// - /// Create a rotation from euler angles - /// - /// The angle around the right axis - /// The angle around the upward axis - /// The angle around the forward axis - /// The resulting quaternion - /// Rotation are appied in the order Z, X, Y. - public static Quaternion Euler(float x, float y, float z) { - return Quaternion.Euler(new Vector3Float(x, y, z)); - } - /// - /// Create a rotation from a vector containing euler angles - /// - /// Vector with the euler angles - /// The resulting quaternion - /// Rotation are appied in the order Z, X, Y. - public static Quaternion Euler(Vector3Float angles) { - Vector3Float euler = angles * AngleFloat.Deg2Rad; - float cx = MathF.Cos(euler.horizontal * 0.5f); - float sx = MathF.Sin(euler.horizontal * 0.5f); - float cy = MathF.Cos(euler.vertical * 0.5f); - float sy = MathF.Sin(euler.vertical * 0.5f); - float cz = MathF.Cos(euler.depth * 0.5f); - float sz = MathF.Sin(euler.depth * 0.5f); - - // Unity uses intrinsic Z, then X, then Y -> q = Qy * Qx * Qz - Quaternion q; - q.w = cy * cx * cz + sy * sx * sz; - q.x = cy * sx * cz + sy * cx * sz; - q.y = sy * cx * cz - cy * sx * sz; - q.z = cy * cx * sz - sy * sx * cz; - return q; - } - - /// - /// Multiply two quaternions - /// - /// - /// - /// The resulting rotation - public static Quaternion operator *(Quaternion q1, Quaternion q2) { - return new Quaternion( - q1.x * q2.w + q1.y * q2.z - q1.z * q2.y + q1.w * q2.x, - -q1.x * q2.z + q1.y * q2.w + q1.z * q2.x + q1.w * q2.y, - q1.x * q2.y - q1.y * q2.x + q1.z * q2.w + q1.w * q2.z, - -q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w); - } - - /// - /// Rotate a vector using this quaterion - /// - /// The rotation - /// The vector to rotate - /// The rotated vector - public static Vector3Float operator *(Quaternion q, Vector3Float v) { - float num = q.x * 2; - float num2 = q.y * 2; - float num3 = q.z * 2; - float num4 = q.x * num; - float num5 = q.y * num2; - float num6 = q.z * num3; - float num7 = q.x * num2; - float num8 = q.x * num3; - float num9 = q.y * num3; - float num10 = q.w * num; - float num11 = q.w * num2; - float num12 = q.w * num3; - - float px = v.horizontal; - float py = v.vertical; - float pz = v.depth; - float rx = - (1 - (num5 + num6)) * px + (num7 - num12) * py + (num8 + num11) * pz; - float ry = - (num7 + num12) * px + (1 - (num4 + num6)) * py + (num9 - num10) * pz; - float rz = - (num8 - num11) * px + (num9 + num10) * py + (1 - (num4 + num5)) * pz; - Vector3Float result = new(rx, ry, rz); - return result; - } - - /// - /// The inverse of quaterion - /// - /// The quaternion for which the inverse is - /// needed The inverted quaternion - public static Quaternion Inverse(Quaternion q) { - float n = MathF.Sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w); - return new Quaternion(-q.x / n, -q.y / n, -q.z / n, q.w / n); - } - - /// - /// A rotation which looks in the given direction - /// - /// The look direction - /// The up direction - /// The look rotation - public static Quaternion LookRotation(Vector3Float forward, Vector3Float up) { - Vector3Float nForward = forward.normalized; - Vector3Float nRight = Vector3Float.Normalize(Vector3Float.Cross(up, nForward)); - Vector3Float nUp = Vector3Float.Cross(nForward, nRight); - float m00 = nRight.horizontal; // x; - float m01 = nRight.vertical; // y; - float m02 = nRight.depth; // z; - float m10 = nUp.horizontal; // x; - float m11 = nUp.vertical; // y; - float m12 = nUp.depth; // z; - float m20 = nForward.horizontal; // x; - float m21 = nForward.vertical; // y; - float m22 = nForward.depth; // z; - - float num8 = (m00 + m11) + m22; - float x, y, z, w; - if (num8 > 0) { - float num = MathF.Sqrt(num8 + 1); - w = num * 0.5f; - num = 0.5f / num; - x = (m12 - m21) * num; - y = (m20 - m02) * num; - z = (m01 - m10) * num; - return new Quaternion(x, y, z, w); - } - if ((m00 >= m11) && (m00 >= m22)) { - float num7 = MathF.Sqrt(((1 + m00) - m11) - m22); - float num4 = 0.5F / num7; - x = 0.5f * num7; - y = (m01 + m10) * num4; - z = (m02 + m20) * num4; - w = (m12 - m21) * num4; - return new Quaternion(x, y, z, w); - } - if (m11 > m22) { - float num6 = MathF.Sqrt(((1 + m11) - m00) - m22); - float num3 = 0.5F / num6; - x = (m10 + m01) * num3; - y = 0.5F * num6; - z = (m21 + m12) * num3; - w = (m20 - m02) * num3; - return new Quaternion(x, y, z, w); - } - float num5 = MathF.Sqrt(((1 + m22) - m00) - m11); - float num2 = 0.5F / num5; - x = (m20 + m02) * num2; - y = (m21 + m12) * num2; - z = 0.5F * num5; - w = (m01 - m10) * num2; - return new Quaternion(x, y, z, w); - } - - /// - /// Creates a quaternion with the given forward direction with up = - /// Vector3::up - /// - /// The look direction - /// The rotation for this direction - /// For the rotation, Vector::up is used for the up direction. - /// Note: if the forward direction == Vector3::up, the result is - /// Quaternion::identity - public static Quaternion LookRotation(Vector3Float forward) { - Vector3Float up = new(0, 1, 0); - return LookRotation(forward, up); - } - - /// - /// Calculat the rotation from on vector to another - /// - /// The from direction - /// The to direction - /// The rotation from the first to the second vector - public static Quaternion FromToRotation(Vector3Float fromDirection, Vector3Float toDirection) { - Vector3Float axis = Vector3Float.Cross(fromDirection, toDirection); - axis = axis.normalized; - AngleFloat angle = Vector3Float.SignedAngle(fromDirection, toDirection, axis); - Quaternion rotation = AngleAxis(angle, axis); - return rotation; - - } - - /// - /// Rotate form one orientation to anther with a maximum amount of degrees - /// - /// The from rotation - /// The destination rotation - /// The maximum amount of degrees to - /// rotate The possibly limited rotation - public static Quaternion RotateTowards(Quaternion from, Quaternion to, - float maxDegreesDelta) { - float num = Quaternion.UnsignedAngle(from, to); - if (num == 0) { - return to; - } - float t = MathF.Min(1, maxDegreesDelta / num); - return SlerpUnclamped(from, to, t); - - } - - /// - /// Convert an angle/axis representation to a quaternion - /// - /// The angle - /// The axis - /// The resulting quaternion - public static Quaternion AngleAxis(AngleFloat angle, Vector3Float axis) { - if (axis.sqrMagnitude == 0.0f) - return Quaternion.identity; - - float radians = angle.inRadians; - radians *= 0.5f; - - Vector3Float axis2 = axis * MathF.Sin(radians); - float x = axis2.horizontal; // x; - float y = axis2.vertical; // y; - float z = axis2.depth; // z; - float w = MathF.Cos(radians); - - return new Quaternion(x, y, z, w).normalized; - } - /// - /// Convert this quaternion to angle/axis representation - /// - /// A pointer to the angle for the result - /// A pointer to the axis for the result - public readonly void ToAngleAxis(out AngleFloat angle, out Vector3Float axis) { - Quaternion q1 = (MathF.Abs(this.w) > 1.0f) ? this.normalized : this; - angle = AngleFloat.Radians(2.0f * MathF.Acos(q1.w)); // angle - float den = MathF.Sqrt(1.0F - q1.w * q1.w); - if (den > 0.0001f) { - axis = Vector3Float.Normalize(q1.xyz / den); - } - else { - // This occurs when the angle is zero. - // Not a problem: just set an arbitrary normalized axis. - axis = Vector3Float.right; - } - } - - /// - /// Get the angle between two orientations - /// - /// The first orientation - /// The second orientation - /// The smallest angle in degrees between the two - /// orientations - public static float UnsignedAngle(Quaternion q1, Quaternion q2) { - // float f = Dot(q1, q2); - // return MathF.Acos(MathF.Min(MathF.Abs(f), 1)) * 2 * AngleFloat.Rad2Deg; - - float dot = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w; - dot = MathF.Min(MathF.Max(dot, -1f), 1f); - return 2f * MathF.Acos(MathF.Abs(dot)) * (180f / MathF.PI); - } - - /// - /// Sherical lerp between two rotations - /// - /// The first rotation - /// The second rotation - /// The factor between 0 and 1. - /// The resulting rotation - /// A factor 0 returns rotation1, factor1 returns rotation2. - public static Quaternion Slerp(Quaternion a, - Quaternion b, float t) { - if (t > 1) - t = 1; - if (t < 0) - t = 0; - return SlerpUnclamped(a, b, t); - } - - /// - /// Unclamped sherical lerp between two rotations - /// - /// The first rotation - /// The second rotation - /// The factor - /// The resulting rotation - /// A factor 0 returns rotation1, factor1 returns rotation2. - /// Values outside the 0..1 range will result in extrapolated rotations - public static Quaternion SlerpUnclamped(Quaternion a, - Quaternion b, float t) { - // if either input is zero, return the other. - if (a.sqrMagnitude == 0.0f) { - if (b.sqrMagnitude == 0.0f) { - return identity; - } - return b; - } - else if (b.sqrMagnitude == 0.0f) { - return a; - } - - Vector3Float axyz = a.xyz; - Vector3Float bxyz = b.xyz; - float cosHalfAngle = a.w * b.w + Vector3Float.Dot(axyz, bxyz); - - Quaternion b2 = b; - if (cosHalfAngle >= 1.0f || cosHalfAngle <= -1.0f) { - // angle = 0.0f, so just return one input. - return a; - } - else if (cosHalfAngle < 0.0f) { - b2.x = -b.x; - b2.y = -b.y; - b2.z = -b.z; - b2.w = -b.w; - cosHalfAngle = -cosHalfAngle; - } - - float blendA; - float blendB; - if (cosHalfAngle < 0.99f) { - // do proper slerp for big angles - float halfAngle = MathF.Acos(cosHalfAngle); - float sinHalfAngle = MathF.Sin(halfAngle); - float oneOverSinHalfAngle = 1.0F / sinHalfAngle; - blendA = MathF.Sin(halfAngle * (1.0F - t)) * oneOverSinHalfAngle; - blendB = MathF.Sin(halfAngle * t) * oneOverSinHalfAngle; - } - else { - // do lerp if angle is really small. - blendA = 1.0f - t; - blendB = t; - } - Vector3Float v = axyz * blendA + b2.xyz * blendB; - Quaternion result = - new(v.horizontal, v.vertical, v.depth, blendA * a.w + blendB * b2.w); - if (result.sqrMagnitude > 0.0f) - return result.normalized; - else - return Quaternion.identity; - } - - /// - /// Convert this quaternion to angle/axis representation - /// - /// A pointer to the angle for the result - /// A pointer to the axis for the result - public readonly void ToAngleAxis(out float angle, out Vector3Float axis) { - ToAxisAngleRad(this, out axis, out angle); - angle *= AngleFloat.Rad2Deg; - } - private static void ToAxisAngleRad(Quaternion q, - out Vector3Float axis, - out float angle) { - Quaternion q1 = (MathF.Abs(q.w) > 1.0f) ? Quaternion.Normalize(q) : q; - angle = 2.0f * MathF.Acos(q1.w); // angle - float den = MathF.Sqrt(1.0F - q1.w * q1.w); - if (den > 0.0001f) { - axis = (q1.xyz / den).normalized; - } - else { - // This occurs when the angle is zero. - // Not a problem: just set an arbitrary normalized axis. - axis = new Vector3Float(1, 0, 0); - } - } - - /// - /// Returns the angle of around the give axis for a rotation - /// - /// The axis around which the angle should be - /// computed The source rotation - /// The signed angle around the axis - public static float GetAngleAround(Vector3Float axis, Quaternion rotation) { - Quaternion secondaryRotation = GetRotationAround(axis, rotation); - secondaryRotation.ToAngleAxis(out float rotationAngle, out Vector3Float rotationAxis); - - // Do the axis point in opposite directions? - if (Vector3Float.Dot(axis, rotationAxis) < 0) - rotationAngle = -rotationAngle; - - return rotationAngle; - } - - /// - /// Returns the rotation limited around the given axis - /// - /// The axis which which the rotation should be - /// limited The source rotation - /// The rotation around the given axis - public static Quaternion GetRotationAround(Vector3Float axis, Quaternion rotation) { - Vector3Float ra = new(rotation.x, rotation.y, rotation.z); // rotation axis - Vector3Float p = Vector3Float.Project( - ra, axis); // return projection ra on to axis (parallel component) - Quaternion twist = new(p.horizontal, p.vertical, p.depth, rotation.w); - twist = Normalize(twist); - return twist; - - } - - /// - /// Swing-twist decomposition of a rotation - /// - /// The base direction for the decomposition - /// The source rotation - /// A pointer to the quaternion for the swing - /// result A pointer to the quaternion for the - /// twist result - static void GetSwingTwist(Vector3Float axis, Quaternion q, - out Quaternion swing, out Quaternion twist) { - twist = GetRotationAround(axis, q); - swing = q * Inverse(twist); - } - - /// - /// Calculate the dot product of two quaternions - /// - /// The first rotation - /// The second rotation - /// - public static float Dot(Quaternion q1, Quaternion q2) { - return q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w; - } - } -#endif - -} \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/src/Spherical.cs b/NanoBrain/LinearAlgebra/src/Spherical.cs deleted file mode 100644 index 318839d..0000000 --- a/NanoBrain/LinearAlgebra/src/Spherical.cs +++ /dev/null @@ -1,279 +0,0 @@ -using System; -using System.Collections.Generic; - -#if UNITY_5_3_OR_NEWER -using Vector3 = UnityEngine.Vector3; -#endif - -namespace LinearAlgebra { - /// - /// A spherical vector - /// - /// This is a struct such that it is a value type and cannot be null - public struct Spherical { - /// - /// Create a spherical vector - /// - /// The distance in meters - /// The direction of the vector - public Spherical(float distance, Direction direction) { - if (distance > 0) { - this.distance = distance; - this.direction = direction; - } - else { - this.distance = -distance; - this.direction = -direction; - } - } - - /// - /// Create spherical vector. All given angles are in degrees - /// - /// The distance in meters - /// The horizontal angle in degrees - /// The vertical angle in degrees - /// - public static Spherical Degrees(float distance, float horizontal, float vertical) { - Direction direction = Direction.Degrees(horizontal, vertical); - Spherical s = new(distance, direction); - return s; - } - - public static Spherical Radians(float distance, float horizontal, float vertical) { - Direction direction = Direction.Radians(horizontal, vertical); - Spherical s = new(distance, direction); - return s; - } - - /// - /// The distance in meters - /// - /// @remark The distance should never be negative - public float distance; - /// - /// The direction of the vector - /// - public Direction direction; - - /// - /// A spherical vector with zero degree angles and distance - /// - public readonly static Spherical zero = new(0, Direction.forward); - /// - /// A normalized forward-oriented vector - /// - public readonly static Spherical forward = new(1, Direction.forward); - -#if UNITY_5_3_OR_NEWER - public static Spherical FromVector3(Vector3 v) { - float distance = v.magnitude; - Direction direction = Direction.FromVector3(v / distance); - return new Spherical(distance, direction); - } - - public readonly Vector3 ToVector3() { - Vector3 v = this.direction.ToVector3(); - v *= this.distance; - return v; - } -#else - public static Spherical FromVector3(Vector3Float v) { - float distance = v.magnitude; - if (distance == 0.0f) - return Spherical.zero; - else { - float verticalAngle = (float)(Math.PI / 2 - Math.Acos(v.vertical / distance)) * AngleFloat.Rad2Deg; - float horizontalAngle = (float)Math.Atan2(v.horizontal, v.depth) * AngleFloat.Rad2Deg; - return Degrees(distance, horizontalAngle, verticalAngle); - } - } - - 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 x = this.distance * sinVertical * sinHorizontal; - // float y = this.distance * cosVertical; - // float z = this.distance * sinVertical * cosHorizontal; - - // 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 { - get { - Spherical r = new() { - distance = 1, - direction = this.direction - }; - return r; - } - } - - public static Spherical operator +(Spherical s1, Spherical s2) { - // let's do it the easy way... - // using vars to be compatible with both unity (Vector3) and native (Vector3Float) - var v1 = s1.ToVector3(); - var v2 = s2.ToVector3(); - var v = v1 + v2; - Spherical r = FromVector3(v); - return r; - } - - public static Spherical operator *(Spherical v, float d) { - Spherical r = new(v.distance * d, v.direction); - return r; - } - - public static bool operator ==(Spherical v1, Spherical v2) { - return (v1.distance == v2.distance && v1.direction == v2.direction); - } - - public static bool operator !=(Spherical v1, Spherical v2) { - return (v1.distance != v2.distance || v1.direction != v2.direction); - } - - public override readonly bool Equals(object o) { - if (o is Spherical s) - return this == s; - return false; - } - - public override readonly int GetHashCode() { - return HashCode.Combine(this.distance, this.direction); - } - - public static float Distance(Spherical v1, Spherical v2) { - // Convert degrees to radians - float thetaARadians = v1.direction.horizontal.inRadians; - float phiARadians = v1.direction.vertical.inRadians;// DegreesToRadians(phiA); - float thetaBRadians = v2.direction.horizontal.inRadians; // DegreesToRadians(thetaB); - float phiBRadians = v2.direction.vertical.inRadians; // DegreesToRadians(phiB); - - // Calculate sine and cosine values - float sinPhiA = MathF.Sin(phiARadians); - float cosPhiA = MathF.Cos(phiARadians); - float sinPhiB = MathF.Sin(phiBRadians); - float cosPhiB = MathF.Cos(phiBRadians); - - // Calculate the cosine of the difference in azimuthal angles - float cosThetaDifference = MathF.Cos(thetaARadians - thetaBRadians); - - // Apply the spherical law of cosines - float distance = MathF.Sqrt( - v1.distance * v1.distance + - v2.distance * v2.distance - - 2 * v1.distance * v2.distance * (sinPhiA * sinPhiB * cosThetaDifference + cosPhiA * cosPhiB) - ); - - return distance; - } - - public static Spherical Average(Spherical v1, Spherical v2) { - const float EPS = 1e-6f; - - // Angles in radians - float a1 = v1.direction.horizontal.inRadians; - float a2 = v2.direction.horizontal.inRadians; - float e1 = v1.direction.vertical.inRadians; - float e2 = v2.direction.vertical.inRadians; - - // Fast path: exactly same direction (allowing wrap for azimuth) -> preserve exact angles - bool sameAz = MathF.Abs(MathF.IEEERemainder(a1 - a2, MathF.PI * 2f)) < EPS; - bool sameEl = MathF.Abs(e1 - e2) < EPS; - if (sameAz && sameEl) { - // Distances may differ; average distance but keep exact angles from v1 - float rAvgExact = 0.5f * (v1.distance + v2.distance); - return new Spherical(rAvgExact, v1.direction); - } - - // Horizontal unit-circle sum - float cx = MathF.Cos(a1) + MathF.Cos(a2); - float cy = MathF.Sin(a1) + MathF.Sin(a2); - - // Vertical as z = sin(el) - float z1 = MathF.Sin(e1); - float z2 = MathF.Sin(e2); - float cz = z1 + z2; - - // Magnitude of summed unit-direction vectors - float sumX = cx; - float sumY = cy; - float sumZ = cz; - float magSum = MathF.Sqrt(sumX * sumX + sumY * sumY + sumZ * sumZ); - - // If the two direction unit-vectors cancel (or nearly), return zero distance. - if (magSum < EPS) { - return Spherical.Radians(0f, 0f, 0f); - } - - // Normalized averaged direction components - float ux = sumX / magSum; - float uy = sumY / magSum; - float uz = sumZ / magSum; - - // Compute averaged angles from normalized vector - float azAvgRad = MathF.Atan2(uy, ux); - float elAvgRad = MathF.Asin(Float.Clamp(uz, -1f, 1f)); - - // Average distance (arithmetic mean) - float rAvg = 0.5f * (v1.distance + v2.distance); - - return Spherical.Radians(rAvg, azAvgRad, elAvgRad); - } - - public static Spherical Sum(List vectors) { - if (vectors == null || vectors.Count == 0) - throw new ArgumentException("vectors must contain at least one element", nameof(vectors)); - -#if UNITY_5_3_OR_NEWER - Vector3 sum = Vector3.zero; -#else - Vector3Float sum = Vector3Float.zero; -#endif - foreach (Spherical v in vectors) - sum += v.ToVector3(); - - return FromVector3(sum); - } - - - public static Spherical Average(List vectors) { - if (vectors == null || vectors.Count == 0) - throw new ArgumentException("vectors must contain at least one element", nameof(vectors)); - -#if UNITY_5_3_OR_NEWER - Vector3 sum = Vector3.zero; -#else - Vector3Float sum = Vector3Float.zero; -#endif - int n = 0; - foreach (Spherical v in vectors) { - sum += v.ToVector3(); - n++; - } - var avg = sum / n; - - // if (avg.sqrMagnitude == 0f) - // return new Spherical(0f, new Direction(AngleFloat.Radians(0f), AngleFloat.Radians(0f))); - // else - return FromVector3(avg); - } - - } -} \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/src/SwingTwist.cs b/NanoBrain/LinearAlgebra/src/SwingTwist.cs deleted file mode 100644 index df6e048..0000000 --- a/NanoBrain/LinearAlgebra/src/SwingTwist.cs +++ /dev/null @@ -1,136 +0,0 @@ -// #if !UNITY_5_3_OR_NEWER -// using UnityEngine; -// #endif - -namespace LinearAlgebra { - - /// - /// An orientation using swing and twist angles - /// - /// The swing rotation - /// The twist rotation - public struct SwingTwist { - public Direction swing; - public AngleFloat twist; - - public SwingTwist(Direction swing, AngleFloat twist) { - this.swing = swing; - this.twist = twist; - } - - /// - /// Create a swing/twist rotation using angles in degrees - /// - /// The swing angle in the horizontal plane in degrees - /// The swing angle in the vertical plan in degrees - /// The twist angle in degrees - /// The swing/twist rotation - public static SwingTwist Degrees(float horizontalSwing, float verticalSwing, float twist) { - Direction swing = Direction.Degrees(horizontalSwing, verticalSwing); - AngleFloat twistAngle = AngleFloat.Degrees(twist); - SwingTwist s = new(swing, twistAngle); - return s; - } - - /// - /// Create a swing/twist rotation using angles in degrees - /// - /// The swing angle in the horizontal plane in degrees - /// The swing angle in the vertical plan in degrees - /// The twist angle in degrees - /// The swing/twist rotation - public static SwingTwist Radians(float horizontalSwing, float verticalSwing, float twist) { - Direction swing = Direction.Radians(horizontalSwing, verticalSwing); - AngleFloat twistAngle = AngleFloat.Radians(twist); - SwingTwist s = new(swing, twistAngle); - return s; - } - -#if UNITY_5_3_OR_NEWER - /// - /// A zero angle rotation - /// - public static readonly SwingTwist zero = Degrees(0, 0, 0); - - public Spherical ToAngleAxis() { - UnityEngine.Quaternion q = this.ToQuaternion(); - q.ToAngleAxis(out float angle, out UnityEngine.Vector3 axis); - Direction direction = Direction.FromVector3(axis); - - Spherical r = new(angle, direction); - return r; - } - - public static SwingTwist FromAngleAxis(Spherical r) { - UnityEngine.Vector3 vectorAxis = r.direction.ToVector3(); - UnityEngine.Quaternion q = UnityEngine.Quaternion.AngleAxis(r.distance, vectorAxis); - return FromQuaternion(q); - } - - /// - /// Convert a quaternion in a swing/twist rotation - /// - /// The quaternion to convert - /// The swing/twist rotation - public static SwingTwist FromQuaternion(UnityEngine.Quaternion q) { - UnityEngine.Vector3 angles = q.eulerAngles; - SwingTwist r = Degrees(angles.y, -angles.x, -angles.z); - return r; - } - - public UnityEngine.Quaternion ToQuaternion() { - UnityEngine.Quaternion q = UnityEngine.Quaternion.Euler(this.swing.vertical.inDegrees, - this.swing.horizontal.inDegrees, - this.twist.inDegrees); - return q; - } -#else - /// - /// A zero angle rotation - /// - public static readonly SwingTwist zero = Degrees(0, 0, 0); - - public Spherical ToAngleAxis() { - LinearAlgebra.Quaternion q = this.ToQuaternion(); - q.ToAngleAxis(out float angle, out Vector3Float axis); - Direction direction = Direction.FromVector3(axis); - - Spherical r = new(angle, direction); - return r; - } - - public static SwingTwist FromAngleAxis(Spherical r) { - Vector3Float vectorAxis = r.direction.ToVector3(); - LinearAlgebra.Quaternion q = LinearAlgebra.Quaternion.AngleAxis(AngleFloat.Degrees(r.distance), vectorAxis); - return FromQuaternion(q); - } - - /// - /// Convert a quaternion in a swing/twist rotation - /// - /// The quaternion to convert - /// The swing/twist rotation - public static SwingTwist FromQuaternion(LinearAlgebra.Quaternion q) { - Vector3Float v = LinearAlgebra.Quaternion.ToAngles(q); - SwingTwist r = Degrees(v.vertical, v.horizontal, v.depth); - return r; - } - - public LinearAlgebra.Quaternion ToQuaternion() { - LinearAlgebra.Quaternion q = LinearAlgebra.Quaternion.Euler(this.swing.vertical.inDegrees, - this.swing.horizontal.inDegrees, - this.twist.inDegrees); - return q; - - } - - public static SwingTwist FromQuat32(Quat32 q32) { - q32.ToAngles(out float right, out float up, out float forward); - SwingTwist r = Degrees(up, right, forward); - return r; - } -#endif - - } - -} \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/src/Vector2Float.cs b/NanoBrain/LinearAlgebra/src/Vector2Float.cs deleted file mode 100644 index ac1867c..0000000 --- a/NanoBrain/LinearAlgebra/src/Vector2Float.cs +++ /dev/null @@ -1,479 +0,0 @@ -using System; -using System.Numerics; - -namespace LinearAlgebra { - - /* - public struct Vector2Int { - public int horizontal; - public int vertical; - - public Vector2Int(int horizontal, int vertical) { - this.horizontal = horizontal; - this.vertical = vertical; - } - - /// - /// A vector with zero for all axis - /// - public static readonly Vector2Int zero = new(0, 0); - /// - /// A vector with values (1, 1) - /// - public static readonly Vector2Int one = new(1, 1); - /// - /// A vector with values (0, 1) - /// - public static readonly Vector2Int up = new(0, 1); - /// - /// A vector with values (0, -1) - /// - public static readonly Vector2Int down = new(0, -1); - /// - /// A vector with values (0, 1) - /// - public static readonly Vector2Int forward = new(0, 1); - /// - /// A vector with values (0, -1) - /// - public static readonly Vector2Int back = new(0, -1); - /// - /// A vector3 with values (-1, 0) - /// - public static readonly Vector2Int left = new(-1, 0); - /// - /// A vector with values (1, 0) - /// - public static readonly Vector2Int right = new(1, 0); - - /// - /// Tests if the vector has equal values as the given vector - /// - /// The vector to compare to - /// true if the vector values are equal - public readonly bool Equals(Vector2Int v) => this.horizontal == v.horizontal && vertical == v.vertical; - - /// - /// Tests if the vector is equal to the given object - /// - /// The object to compare to - /// false when the object is not a Vector2 or does not have equal values - public override readonly bool Equals(object obj) { - if (obj is not Vector2Int v) - return false; - - return (this.horizontal == v.horizontal && this.vertical == v.vertical); - } - - /// - /// Tests if the two vectors have equal values - /// - /// The first vector - /// The second vector - /// truewhen the vectors have equal values - /// Note that this uses a Float equality check which cannot be not exact in all cases. - /// In most cases it is better to check if the Vector2.Distance between the vectors is smaller than Float.epsilon - /// Or more efficient: (v1 - v2).sqrMagnitude < Float.sqrEpsilon - public static bool operator ==(Vector2Int v1, Vector2Int v2) { - return (v1.horizontal == v2.horizontal && v1.vertical == v2.vertical); - } - /// - /// Tests if two vectors have different values - /// - /// The first vector - /// The second vector - /// truewhen the vectors have different values - /// Note that this uses a Float equality check which cannot be not exact in all case. - /// In most cases it is better to check if the Vector2.Distance between the vectors is smaller than Float.epsilon. - /// Or more efficient: (v1 - v2).sqrMagnitude < Float.sqrEpsilon - public static bool operator !=(Vector2Int v1, Vector2Int v2) { - return (v1.horizontal != v2.horizontal || v1.vertical != v2.vertical); - } - public readonly float magnitude { - get { - int h = this.horizontal; - int v = this.vertical; - return MathF.Sqrt(h * h + v * v); - } - } - - public static float MagnitudeOf(Vector2Int v) { - return v.magnitude; - } - - public static Vector2Int operator -(Vector2Int v1, Vector2Int v2) { - return new Vector2Int(v1.horizontal - v2.horizontal, v1.vertical - v2.vertical); - } - public static Vector2Int operator +(Vector2Int v1, Vector2Int v2) { - return new Vector2Int(v1.horizontal + v2.horizontal, v1.vertical + v2.vertical); - } - - public static float Distance(Vector2Int v1, Vector2Int v2) { - return (v1 - v2).magnitude; - } - } - - public struct Vector2Float { - public float horizontal; - public float vertical; - - public Vector2Float(float horizontal, float vertical) { - this.horizontal = horizontal; - this.vertical = vertical; - } - - public readonly float magnitude { - get { - float h = this.horizontal; - float v = this.vertical; - return MathF.Sqrt(h * h + v * v); - } - } - - public static Vector2Float operator -(Vector2Float v1, Vector2Float v2) { - return new Vector2Float(v1.horizontal - v2.horizontal, v1.vertical - v2.vertical); - } - - public static float Distance(Vector2Float v1, Vector2Float v2) { - return (v1 - v2).magnitude; - } - } - */ - - /// - /// 2-dimensional vectors - /// - public struct Vector2Float { - - /// - /// The right axis of the vector - /// - public float horizontal; // left/right - /// - /// The upward/forward axis of the vector - /// - public float vertical; // forward/backward - // directions are to be inline with Vector3 as much as possible... - - /// - /// Create a new 2-dimensional vector - /// - /// x axis value - /// y axis value - public Vector2Float(float x, float y) { - this.horizontal = x; - this.vertical = y; - } - - /// - /// Convert a Vector2Int into a Vector2Float - /// - /// The Vector2Int - public Vector2Float(Vector2Int v) { - this.horizontal = v.horizontal; - this.vertical = v.vertical; - } - - /// - /// A vector with zero for all axis - /// - public static readonly Vector2Float zero = new Vector2Float(0, 0); - /// - /// A vector with values (1, 1) - /// - public static readonly Vector2Float one = new Vector2Float(1, 1); - /// - /// A vector with values (0, 1) - /// - public static readonly Vector2Float up = new Vector2Float(0, 1); - /// - /// A vector with values (0, -1) - /// - public static readonly Vector2Float down = new Vector2Float(0, -1); - /// - /// A vector with values (0, 1) - /// - public static readonly Vector2Float forward = new Vector2Float(0, 1); - /// - /// A vector with values (0, -1) - /// - public static readonly Vector2Float back = new Vector2Float(0, -1); - /// - /// A vector3 with values (-1, 0) - /// - public static readonly Vector2Float left = new Vector2Float(-1, 0); - /// - /// A vector with values (1, 0) - /// - public static readonly Vector2Float right = new Vector2Float(1, 0); - - /// - /// The squared length of this vector - /// - /// The squared length - /// The squared length is computationally simpler than the real length. - /// Think of Pythagoras A^2 + B^2 = C^2. - /// This leaves out the calculation of the squared root of C. - public readonly float sqrMagnitude => horizontal * horizontal + vertical * vertical; - public static float SqrMagnitudeOf(Vector2Float v) { - return v.sqrMagnitude; - } - - /// - /// The length of this vector - /// - /// The length of this vector - public readonly float magnitude => MathF.Sqrt(horizontal * horizontal + vertical * vertical); - public static float MagnitudeOf(Vector2Float v) { - return v.magnitude; - } - - /// - /// Convert the vector to a length of a 1 - /// - /// The vector with length 1 - public Vector2Float normalized { - get { - float l = magnitude; - Vector2Float v = zero; - if (l > Float.epsilon) - v = this / l; - return v; - } - } - public static Vector2Float Normalize(Vector2Float v) { - return v.normalized; - } - - /// - /// Add two vectors - /// - /// The first vector - /// The second vector - /// The result of adding the two vectors - public static Vector2Float operator +(Vector2Float v1, Vector2Float v2) { - Vector2Float v = new Vector2Float(v1.horizontal + v2.horizontal, v1.vertical + v2.vertical); - return v; - } - - /// - /// Subtract two vectors - /// - /// The first vector - /// The second vector - /// The result of adding the two vectors - public static Vector2Float operator -(Vector2Float v1, Vector2Float v2) { - Vector2Float v = new Vector2Float(v1.horizontal - v2.horizontal, v1.vertical - v2.vertical); - return v; - } - - /// - /// Negate the vector - /// - /// The vector to negate - /// The negated vector - /// This will result in a vector pointing in the opposite direction - public static Vector2Float operator -(Vector2Float v1) { - Vector2Float v = new Vector2Float(-v1.horizontal, -v1.vertical); - return v; - } - - /// - /// Scale a vector uniformly down - /// - /// The vector to scale - /// The scaling factor - /// The scaled vector - /// Each component of the vector will be devided by the same factor. - public static Vector2Float operator /(Vector2Float v, float f) { - Vector2Float r = new(v.horizontal / f, v.vertical / f); - return r; - } - - - /// - /// Scale a vector uniformly up - /// - /// The vector to scale - /// The scaling factor - /// The scaled vector - /// Each component of the vector will be multipled with the same factor. - public static Vector2Float operator *(Vector2Float v1, float f) { - Vector2Float v = new Vector2Float(v1.horizontal * f, v1.vertical * f); - return v; - } - - /// - /// Scale a vector uniformly up - /// - /// The scaling factor - /// The vector to scale - /// The scaled vector - /// Each component of the vector will be multipled with the same factor. - public static Vector2Float operator *(float f, Vector2Float v1) { - Vector2Float v = new Vector2Float(f * v1.horizontal, f * v1.vertical); - return v; - } - - /// @brief Scale the vector using another vector - /// @param v1 The vector to scale - /// @param v2 A vector with the scaling factors - /// @return The scaled vector - /// @remark Each component of the vector v1 will be multiplied with the - /// matching component from the scaling vector v2. - public static Vector2Float Scale(Vector2Float v1, Vector2Float v2) { - return new Vector2Float(v1.horizontal * v2.horizontal, v1.vertical * v2.vertical); - } - - /// - /// Tests if the vector has equal values as the given vector - /// - /// The vector to compare to - /// true if the vector values are equal - //public readonly bool Equals(Vector2Float v1) => horizontal == v1.horizontal && vertical == v1.vertical; - - /// - /// Tests if the two vectors have equal values - /// - /// The first vector - /// The second vector - /// truewhen the vectors have equal values - /// Note that this uses a Float equality check which cannot be not exact in all cases. - /// In most cases it is better to check if the Vector2.Distance between the vectors is smaller than Float.epsilon - /// Or more efficient: (v1 - v2).sqrMagnitude < Float.sqrEpsilon - public static bool operator ==(Vector2Float v1, Vector2Float v2) { - return (v1.horizontal == v2.horizontal && v1.vertical == v2.vertical); - } - - /// - /// Tests if two vectors have different values - /// - /// The first vector - /// The second vector - /// truewhen the vectors have different values - /// Note that this uses a Float equality check which cannot be not exact in all case. - /// In most cases it is better to check if the Vector2.Distance between the vectors is smaller than Float.epsilon. - /// Or more efficient: (v1 - v2).sqrMagnitude < Float.sqrEpsilon - public static bool operator !=(Vector2Float v1, Vector2Float v2) { - return (v1.horizontal != v2.horizontal || v1.vertical != v2.vertical); - } - - /// - /// Tests if the vector is equal to the given object - /// - /// The object to compare to - /// false when the object is not a Vector2 or does not have equal values - public override readonly bool Equals(object obj) { - if (obj is not Vector2Float v) - return false; - - return (horizontal == v.horizontal && vertical == v.vertical); - } - - /// - /// Get an hash code for the vector - /// - /// The hash code - public override readonly int GetHashCode() { - return HashCode.Combine(horizontal, vertical); - } - - /// - /// Get the distance between two vectors - /// - /// The first vector - /// The second vector - /// The distance between the two vectors - public static float Distance(Vector2Float v1, Vector2Float v2) { - float x = v1.horizontal - v2.horizontal; - float y = v1.vertical - v2.vertical; - float d = (float)Math.Sqrt(x * x + y * y); - return d; - } - - /// - /// The dot product of two vectors - /// - /// The first vector - /// The second vector - /// The dot product of the two vectors - public static float Dot(Vector2Float v1, Vector2Float v2) { - return v1.horizontal * v2.horizontal + v1.vertical * v2.vertical; - } - - /// - /// Calculate the signed angle between two vectors. - /// - /// The starting vector - /// The ending vector - /// The axis to rotate around - /// The signed angle in degrees - public static float SignedAngle(Vector2Float from, Vector2Float to) { - //float sign = Math.Sign(v1.y * v2.x - v1.x * v2.y); - //return Vector2.Angle(v1, v2) * sign; - - float sqrMagFrom = from.sqrMagnitude; - float sqrMagTo = to.sqrMagnitude; - - if (sqrMagFrom == 0 || sqrMagTo == 0) - return 0; - //if (!isfinite(sqrMagFrom) || !isfinite(sqrMagTo)) - // return nanf(""); - - float angleFrom = (float)Math.Atan2(from.vertical, from.horizontal); - float angleTo = (float)Math.Atan2(to.vertical, to.horizontal); - return -(angleTo - angleFrom) * AngleFloat.Rad2Deg; - } - - public static float UnsignedAngle(Vector2Float from, Vector2Float to) { - return MathF.Abs(SignedAngle(from, to)); - } - - /// - /// Rotates the vector with the given angle - /// - /// The vector to rotate - /// The angle in degrees - /// - public static Vector2Float Rotate(Vector2Float v1, AngleFloat angle) { - float sin = (float)Math.Sin(angle.inRadians); - float cos = (float)Math.Cos(angle.inRadians); - // float sin = AngleFloat.Sin(angle); - // float cos = AngleFloat.Cos(angle); - - float tx = v1.horizontal; - float ty = v1.vertical; - Vector2Float v = new Vector2Float() { - horizontal = (cos * tx) - (sin * ty), - vertical = (sin * tx) + (cos * ty) - }; - return v; - } - - /// - /// Lerp between two vectors - /// - /// The from vector - /// The to vector - /// The interpolation distance [0..1] - /// The lerped vector - /// The factor f is unclamped. Value 0 matches the *v1* vector, Value 1 - /// matches the *v2* vector Value -1 is *v1* vector minus the difference - /// between *v1* and *v2* etc. - public static Vector2Float Lerp(Vector2Float v1, Vector2Float v2, float f) { - Vector2Float v = v1 + (v2 - v1) * f; - return v; - } - - /// - /// Map interval of angles between vectors [0..Pi] to interval [0..1] - /// - /// The first vector - /// The second vector - /// The resulting factor in interval [0..1] - /// Vectors a and b must be normalized - public static float ToFactor(Vector2Float v1, Vector2Float v2) { - return (1 - Vector2Float.Dot(v1, v2)) / 2; - } - } -} \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/src/Vector2Int.cs b/NanoBrain/LinearAlgebra/src/Vector2Int.cs deleted file mode 100644 index ed68e8b..0000000 --- a/NanoBrain/LinearAlgebra/src/Vector2Int.cs +++ /dev/null @@ -1,185 +0,0 @@ -using System; - -namespace LinearAlgebra { - - public struct Vector2Int { - public int horizontal; - public int vertical; - - public Vector2Int(int horizontal, int vertical) { - this.horizontal = horizontal; - this.vertical = vertical; - } - - /// - /// A vector with zero for all axis - /// - public static readonly Vector2Int zero = new(0, 0); - /// - /// A vector with values (1, 1) - /// - public static readonly Vector2Int one = new(1, 1); - /// - /// A vector with values (0, 1) - /// - public static readonly Vector2Int up = new(0, 1); - /// - /// A vector with values (0, -1) - /// - public static readonly Vector2Int down = new(0, -1); - /// - /// A vector with values (0, 1) - /// - public static readonly Vector2Int forward = new(0, 1); - /// - /// A vector with values (0, -1) - /// - public static readonly Vector2Int back = new(0, -1); - /// - /// A vector3 with values (-1, 0) - /// - public static readonly Vector2Int left = new(-1, 0); - /// - /// A vector with values (1, 0) - /// - public static readonly Vector2Int right = new(1, 0); - - /* - /// - /// Get an hash code for the vector - /// - /// The hash code - public override int GetHashCode() { - return (this.horizontal, this.vertical).GetHashCode(); - } - - /// - /// Tests if the vector has equal values as the given vector - /// - /// The vector to compare to - /// true if the vector values are equal - public readonly bool Equals(Vector2Int v) => this.horizontal == v.horizontal && vertical == v.vertical; - - */ - - /// - /// Tests if the two vectors have equal values - /// - /// The first vector - /// The second vector - /// truewhen the vectors have equal values - /// Note that this uses a Float equality check which cannot be not exact in all cases. - /// In most cases it is better to check if the Vector2.Distance between the vectors is smaller than Float.epsilon - /// Or more efficient: (v1 - v2).sqrMagnitude < Float.sqrEpsilon - public static bool operator ==(Vector2Int v1, Vector2Int v2) { - return (v1.horizontal == v2.horizontal && v1.vertical == v2.vertical); - } - /// - /// Tests if two vectors have different values - /// - /// The first vector - /// The second vector - /// truewhen the vectors have different values - /// Note that this uses a Float equality check which cannot be not exact in all case. - /// In most cases it is better to check if the Vector2.Distance between the vectors is smaller than Float.epsilon. - /// Or more efficient: (v1 - v2).sqrMagnitude < Float.sqrEpsilon - public static bool operator !=(Vector2Int v1, Vector2Int v2) { - return (v1.horizontal != v2.horizontal || v1.vertical != v2.vertical); - } - - /// - /// Tests if the vector is equal to the given object - /// - /// The object to compare to - /// false when the object is not a Vector2 or does not have equal values - public override readonly bool Equals(object obj) { - if (obj is not Vector2Int v) - return false; - - return (this.horizontal == v.horizontal && this.vertical == v.vertical); - } - - /// - /// Get an hash code for the vector - /// - /// The hash code - public override readonly int GetHashCode() { - return HashCode.Combine(horizontal, vertical); - } - - public readonly float sqrMagnitude => this.horizontal * this.horizontal + this.vertical * this.vertical; - - public static float SqrMagnitudeOf(Vector2Int v) { - return v.sqrMagnitude; - } - - public readonly float magnitude => - MathF.Sqrt(this.horizontal * this.horizontal + this.vertical * this.vertical); - - public static float MagnitudeOf(Vector2Int v) { - return v.magnitude; - } - - /// @brief Convert the vector to a length of 1 - /// @return The vector normalized to a length of 1 - public readonly Vector2Float normalized { - get { - float l = magnitude; - Vector2Float v = Vector2Float.zero; - if (l > Float.epsilon) - v = new Vector2Float(this) / l; - return v; - } - } - /// @brief Convert the vector to a length of 1 - /// @param v The vector to convert - /// @return The vector normalized to a length of 1 - public static Vector2Float Normalize(Vector2Int v) { - float num = v.magnitude; - Vector2Float result = Vector2Float.zero; - if (num > Float.epsilon) - result = new Vector2Float(v) / num; - - return result; - } - - public static Vector2Int operator -(Vector2Int v) { - return new Vector2Int(-v.horizontal, -v.vertical); - } - - public static Vector2Int operator -(Vector2Int v1, Vector2Int v2) { - return new Vector2Int(v1.horizontal - v2.horizontal, v1.vertical - v2.vertical); - } - public static Vector2Int operator +(Vector2Int v1, Vector2Int v2) { - return new Vector2Int(v1.horizontal + v2.horizontal, v1.vertical + v2.vertical); - } - - public static Vector2Int operator /(Vector2Int v, int f) { - return new Vector2Int(v.horizontal / f, v.vertical / f); - } - - public static Vector2Int operator *(Vector2Int v1, int d) { - return new Vector2Int(v1.horizontal * d, v1.vertical * d); - } - - public static Vector2Int operator *(int d, Vector2Int v1) { - return new Vector2Int(d * v1.horizontal, d * v1.vertical); - } - - public static Vector2Int Scale(Vector2Int v1, Vector2Int v2) { - return new Vector2Int(v1.horizontal * v2.horizontal, v1.vertical * v2.vertical); - } - - /// @brief The dot product of two vectors - /// @param v1 The first vector - /// @param v2 The second vector - /// @return The dot product of the two vectors - public static int Dot(Vector2Int v1, Vector2Int v2) { - return v1.horizontal * v2.horizontal + v1.vertical * v2.vertical; - } - - public static float Distance(Vector2Int v1, Vector2Int v2) { - return (v1 - v2).magnitude; - } - } -} \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/src/Vector3Float.cs b/NanoBrain/LinearAlgebra/src/Vector3Float.cs deleted file mode 100644 index bcf8626..0000000 --- a/NanoBrain/LinearAlgebra/src/Vector3Float.cs +++ /dev/null @@ -1,402 +0,0 @@ -//#if !UNITY_5_3_OR_NEWER -using System; - -namespace LinearAlgebra { - /* - public struct Vector3Float { - public float horizontal; - public float vertical; - public float depth; - - public Vector3Float(float horizontal, float vertical, float depth) { - this.horizontal = horizontal; - this.vertical = vertical; - this.depth = depth; - } - - /// - /// A vector with zero for all axis - /// - public static readonly Vector3Float zero = new(0, 0, 0); - - public readonly float magnitude { - get => (float)Math.Sqrt(this.horizontal * this.horizontal + this.vertical * this.vertical + this.depth * this.depth); - } - - /// - /// Convert the vector to a length of a 1 - /// - /// The vector with length 1 - public readonly Vector3Float normalized { - get { - float l = magnitude; - Vector3Float v = zero; - if (l > Float.epsilon) - v = this / l; - return v; - } - } - - - public static Vector3Float operator *(Vector3Float v, float f) { - Vector3Float r = new(v.horizontal * f, v.vertical * f, v.depth * f); - return r; - } - public static Vector3Float operator /(Vector3Float v, float f) { - Vector3Float r = new(v.horizontal / f, v.vertical / f, v.depth / f); - return r; - } - - public static float Dot(Vector3Float v1, Vector3Float v2) { - return v1.horizontal * v2.horizontal + v1.vertical * v2.vertical + - v1.depth * v2.depth; - } - - const float epsilon = 1E-05f; - public static Vector3Float Project(Vector3Float v, Vector3Float n) { - float sqrMagnitude = Dot(n, n); - if (sqrMagnitude < epsilon) - return zero; - else { - float dot = Dot(v, n); - Vector3Float r = n * dot; - r /= sqrMagnitude; - return r; - } - - } - } - */ - - /// - /// 3-dimensional vectors - /// - /// This uses the right-handed coordinate system. - public struct Vector3Float { - - /// - /// The right axis of the vector - /// - public float horizontal; //> left/right - /// - /// The upward axis of the vector - /// - public float vertical; //> up/down - /// - /// The forward axis of the vector - /// - public float depth; //> forward/backward - - /// - /// Create a new 3-dimensional vector - /// - /// x axis value - /// y axis value - /// z axis value - public Vector3Float(float horizontal, float vertical, float depth) { - this.horizontal = horizontal; - this.vertical = vertical; - this.depth = depth; - } - - public Vector3Float(Vector3Int v) { - this.horizontal = v.horizontal; - this.vertical = v.vertical; - this.depth = v.depth; - } - - public static Vector3Float FromSpherical(Spherical s) { - float verticalRad = (AngleFloat.deg90 - s.direction.vertical).inRadians; - float horizontalRad = s.direction.horizontal.inRadians; - float cosVertical = MathF.Cos(verticalRad); - float sinVertical = MathF.Sin(verticalRad); - float cosHorizontal = MathF.Cos(horizontalRad); - float sinHorizontal = MathF.Sin(horizontalRad); - - float horizontal = s.distance * sinVertical * sinHorizontal; - float vertical = s.distance * cosVertical; - float depth = s.distance * sinVertical * cosHorizontal; - return new Vector3Float(horizontal, vertical, depth); - } - - public override string ToString() { - return $"({this.horizontal}, {this.vertical}, {this.depth})"; - } - - /// - /// A vector with zero for all axis - /// - public static readonly Vector3Float zero = new Vector3Float(0, 0, 0); - /// - /// A vector with one for all axis - /// - public static readonly Vector3Float one = new Vector3Float(1, 1, 1); - /// - /// A Vector3Float with values (-1, 0, 0) - /// - public static readonly Vector3Float left = new Vector3Float(-1, 0, 0); - /// - /// A vector with values (1, 0, 0) - /// - public static readonly Vector3Float right = new Vector3Float(1, 0, 0); - /// - /// A vector with values (0, -1, 0) - /// - public static readonly Vector3Float down = new Vector3Float(0, -1, 0); - /// - /// A vector with values (0, 1, 0) - /// - public static readonly Vector3Float up = new Vector3Float(0, 1, 0); - /// - /// A vector with values (0, 0, -1) - /// - public static readonly Vector3Float back = new Vector3Float(0, -1, 0); - /// - /// A vector with values (0, 0, 1) - /// - public static readonly Vector3Float forward = new Vector3Float(0, 1, 0); - - /// @brief The vector length - /// @return The vector length - public readonly float magnitude => MathF.Sqrt(horizontal * horizontal + vertical * vertical + depth * depth); - /// - /// The vector length - /// - /// The vector for which you need the length - /// The vector length - public static float MagnitudeOf(Vector3Float v) { - return v.magnitude; - } - - /// @brief The squared vector length - /// @return The squared vector length - /// @remark The squared length is computationally simpler than the real - /// length. Think of Pythagoras A^2 + B^2 = C^2. This leaves out the - /// calculation of the squared root of C. - public readonly float sqrMagnitude => (horizontal * horizontal + vertical * vertical + depth * depth); - - /// - /// The squared vector length - /// - /// The vector for which you need the squared length - /// The squared vector length - /// The squared length is computationally simpler than the real - /// length. Think of Pythagoras A^2 + B^2 = C^2. This leaves out the - /// calculation of the squared root of C. - public static float SqrMagnitudeOf(Vector3Float v) { - return v.sqrMagnitude; - } - - /// @brief Convert the vector to a length of 1 - /// @return The vector normalized to a length of 1 - public readonly Vector3Float normalized { - get { - float l = magnitude; - Vector3Float v = zero; - if (l > Float.epsilon) - v = this / l; - return v; - } - } - /// @brief Convert the vector to a length of 1 - /// @param v The vector to convert - /// @return The vector normalized to a length of 1 - public static Vector3Float Normalize(Vector3Float v) { - float num = v.magnitude; - Vector3Float result = zero; - if (num > Float.epsilon) - result = v / num; - - return result; - } - - /// - /// Negate te vector such that it points in the opposite direction - /// - /// - /// The negated vector - public static Vector3Float operator -(Vector3Float v1) { - Vector3Float v = new(-v1.horizontal, -v1.vertical, -v1.depth); - return v; - } - - /// - /// Subtract two vectors - /// - /// - /// - /// The result of the subtraction - public static Vector3Float operator -(Vector3Float v1, Vector3Float v2) { - Vector3Float v = new(v1.horizontal - v2.horizontal, v1.vertical - v2.vertical, v1.depth - v2.depth); - return v; - } - - /// - /// Add two vectors - /// - /// - /// - /// The result of the addition - public static Vector3Float operator +(Vector3Float v1, Vector3Float v2) { - Vector3Float v = new(v1.horizontal + v2.horizontal, v1.vertical + v2.vertical, v1.depth + v2.depth); - return v; - } - - /// @brief Scale the vector using another vector - /// @param v1 The vector to scale - /// @param v2 A vector with the scaling factors - /// @return The scaled vector - /// @remark Each component of the vector v1 will be multiplied with the - /// matching component from the scaling vector v2. - public static Vector3Float Scale(Vector3Float v1, Vector3Float v2) { - return new Vector3Float(v1.horizontal * v2.horizontal, v1.vertical * v2.vertical, v1.depth * v2.depth); - } - - - public static Vector3Float operator *(Vector3Float v1, float d) { - Vector3Float v = new(v1.horizontal * d, v1.vertical * d, v1.depth * d); - return v; - } - - public static Vector3Float operator *(float d, Vector3Float v1) { - Vector3Float v = new(d * v1.horizontal, d * v1.vertical, d * v1.depth); - return v; - } - - public static Vector3Float operator /(Vector3Float v1, float d) { - Vector3Float v = new(v1.horizontal / d, v1.vertical / d, v1.depth / d); - return v; - } - - - //public bool Equals(Vector3Float v) => (horizontal == v.horizontal && vertical == v.vertical && depth == v.depth); - - public static bool operator ==(Vector3Float v1, Vector3Float v2) { - return (v1.horizontal == v2.horizontal && v1.vertical == v2.vertical && v1.depth == v2.depth); - } - - public static bool operator !=(Vector3Float v1, Vector3Float v2) { - return (v1.horizontal != v2.horizontal || v1.vertical != v2.vertical || v1.depth != v2.depth); - } - - public override readonly bool Equals(object obj) { - if (obj is not Vector3Float v) - return false; - - return (horizontal == v.horizontal && vertical == v.vertical && depth == v.depth); - } - - public override readonly int GetHashCode() { - return HashCode.Combine(horizontal, vertical, depth); - } - - /// @brief The distance between two vectors - /// @param v1 The first vector - /// @param v2 The second vector - /// @return The distance between the two vectors - public static float Distance(Vector3Float v1, Vector3Float v2) { - return (v2 - v1).magnitude; - } - - /// @brief The dot product of two vectors - /// @param v1 The first vector - /// @param v2 The second vector - /// @return The dot product of the two vectors - public static float Dot(Vector3Float v1, Vector3Float v2) { - return v1.horizontal * v2.horizontal + v1.vertical * v2.vertical + v1.depth * v2.depth; - } - - /// @brief The cross product of two vectors - /// @param v1 The first vector - /// @param v2 The second vector - /// @return The cross product of the two vectors - public static Vector3Float Cross(Vector3Float v1, Vector3Float v2) { - return new Vector3Float(v1.vertical * v2.depth - v1.depth * v2.vertical, v1.depth * v2.horizontal - v1.horizontal * v2.depth, - v1.horizontal * v2.vertical - v1.vertical * v2.horizontal); - - } - - /// @brief Project the vector on another vector - /// @param v The vector to project - /// @param n The normal vecto to project on - /// @return The projected vector - public static Vector3Float Project(Vector3Float v, Vector3Float n) { - float sqrMagnitude = Dot(n, n); - if (sqrMagnitude < Float.epsilon) - return zero; - else { - float dot = Dot(v, n); - Vector3Float r = n * dot / sqrMagnitude; - return r; - } - } - - /// @brief Project the vector on a plane defined by a normal orthogonal to the - /// plane. - /// @param v The vector to project - /// @param n The normal of the plane to project on - /// @return Teh projected vector - public static Vector3Float ProjectOnPlane(Vector3Float v, Vector3Float n) { - Vector3Float r = v - Project(v, n); - return r; - } - - /// @brief The angle between two vectors - /// @param v1 The first vector - /// @param v2 The second vector - /// @return The angle between the two vectors - /// @remark This reterns an unsigned angle which is the shortest distance - /// between the two vectors. Use Vector3::SignedAngle if a signed angle is - /// needed. - public static AngleFloat UnsignedAngle(Vector3Float v1, Vector3Float v2) { - float denominator = MathF.Sqrt(v1.sqrMagnitude * v2.sqrMagnitude); - if (denominator < Float.epsilon) - return AngleFloat.zero; - - float dot = Dot(v1, v2); - float fraction = dot / denominator; - if (float.IsNaN(fraction)) - return AngleFloat.Degrees( - fraction); // short cut to returning NaN universally - - float cdot = Float.Clamp(fraction, -1.0f, 1.0f); - float r = MathF.Acos(cdot); - return AngleFloat.Radians(r); - } - /// @brief The signed angle between two vectors - /// @param v1 The starting vector - /// @param v2 The ending vector - /// @param axis The axis to rotate around - /// @return The signed angle between the two vectors - public static AngleFloat SignedAngle(Vector3Float v1, Vector3Float v2, - Vector3Float axis) { - // angle in [0,180] - AngleFloat angle = UnsignedAngle(v1, v2); - - Vector3Float cross = Cross(v1, v2); - float b = Dot(axis, cross); - float signd = b < 0 ? -1.0F : (b > 0 ? 1.0F : 0.0F); - - // angle in [-179,180] - AngleFloat signed_angle = angle * signd; - - return signed_angle; - } - - - /// @brief Lerp (linear interpolation) between two vectors - /// @param v1 The starting vector - /// @param v2 The ending vector - /// @param f The interpolation distance - /// @return The lerped vector - /// @remark The factor f is unclamped. Value 0 matches the vector *v1*, Value - /// 1 matches vector *v2*. Value -1 is vector *v1* minus the difference - /// between *v1* and *v2* etc. - public static Vector3Float Lerp(Vector3Float v1, Vector3Float v2, float f) { - Vector3Float v = v1 + (v2 - v1) * f; - return v; - } - - } -} -//#endif \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/src/Vector3Int.cs b/NanoBrain/LinearAlgebra/src/Vector3Int.cs deleted file mode 100644 index 18edf40..0000000 --- a/NanoBrain/LinearAlgebra/src/Vector3Int.cs +++ /dev/null @@ -1,273 +0,0 @@ -//#if !UNITY_5_3_OR_NEWER -using System; - -namespace LinearAlgebra { - - /// - /// 3-dimensional vectors - /// - /// This uses the right-handed coordinate system. - /// - /// Create a new 3-dimensional vector - /// - /// x axis value - /// y axis value - /// z axis value - public struct Vector3Int { - - /// - /// The right axis of the vector - /// - public int horizontal; //> left/right - /// - /// The upward axis of the vector - /// - public int vertical; //> up/down - /// - /// The forward axis of the vector - /// - public int depth; //> forward/backward - - public Vector3Int(int horizontal, int vertical, int depth) { - this.horizontal = horizontal; - this.vertical = vertical; - this.depth = depth; - } - - /// - /// A vector with zero for all axis - /// - public static readonly Vector3Int zero = new(0, 0, 0); - /// - /// A vector with one for all axis - /// - public static readonly Vector3Int one = new(1, 1, 1); - /// - /// A Vector3Int with values (-1, 0, 0) - /// - public static readonly Vector3Int left = new(-1, 0, 0); - /// - /// A vector with values (1, 0, 0) - /// - public static readonly Vector3Int right = new(1, 0, 0); - /// - /// A vector with values (0, -1, 0) - /// - public static readonly Vector3Int down = new(0, -1, 0); - /// - /// A vector with values (0, 1, 0) - /// - public static readonly Vector3Int up = new(0, 1, 0); - /// - /// A vector with values (0, 0, -1) - /// - public static readonly Vector3Int back = new(0, -1, 0); - /// - /// A vector with values (0, 0, 1) - /// - public static readonly Vector3Int forward = new(0, 1, 0); - - /// @brief The vector length - /// @return The vector length - public readonly float magnitude => MathF.Sqrt(horizontal * horizontal + vertical * vertical + depth * depth); - /// - /// The vector length - /// - /// The vector for which you need the length - /// The vector length - public static float MagnitudeOf(Vector3Int v) { - return v.magnitude; - } - - /// @brief The squared vector length - /// @return The squared vector length - /// @remark The squared length is computationally simpler than the real - /// length. Think of Pythagoras A^2 + B^2 = C^2. This leaves out the - /// calculation of the squared root of C. - public readonly float sqrMagnitude => (horizontal * horizontal + vertical * vertical + depth * depth); - - /// - /// The squared vector length - /// - /// The vector for which you need the squared length - /// The squared vector length - /// The squared length is computationally simpler than the real - /// length. Think of Pythagoras A^2 + B^2 = C^2. This leaves out the - /// calculation of the squared root of C. - public static float SqrMagnitudeOf(Vector3Int v) { - return v.sqrMagnitude; - } - - /// @brief Convert the vector to a length of 1 - /// @return The vector normalized to a length of 1 - public readonly Vector3Float normalized { - get { - float l = magnitude; - Vector3Float v = Vector3Float.zero; - if (l > Float.epsilon) - v = new Vector3Float(this) / l; - return v; - } - } - /// @brief Convert the vector to a length of 1 - /// @param v The vector to convert - /// @return The vector normalized to a length of 1 - public static Vector3Float Normalize(Vector3Int v) { - float num = v.magnitude; - Vector3Float result = Vector3Float.zero; - if (num > Float.epsilon) - result = new Vector3Float(v) / num; - - return result; - } - - /// - /// Negate te vector such that it points in the opposite direction - /// - /// - /// The negated vector - public static Vector3Int operator -(Vector3Int v1) { - Vector3Int v = new(-v1.horizontal, -v1.vertical, -v1.depth); - return v; - } - - /// - /// Subtract two vectors - /// - /// - /// - /// The result of the subtraction - public static Vector3Int operator -(Vector3Int v1, Vector3Int v2) { - Vector3Int v = new(v1.horizontal - v2.horizontal, v1.vertical - v2.vertical, v1.depth - v2.depth); - return v; - } - - /// - /// Add two vectors - /// - /// - /// - /// The result of the addition - public static Vector3Int operator +(Vector3Int v1, Vector3Int v2) { - Vector3Int v = new(v1.horizontal + v2.horizontal, v1.vertical + v2.vertical, v1.depth + v2.depth); - return v; - } - - /// @brief Scale the vector using another vector - /// @param v1 The vector to scale - /// @param v2 A vector with the scaling factors - /// @return The scaled vector - /// @remark Each component of the vector v1 will be multiplied with the - /// matching component from the scaling vector v2. - public static Vector3Int Scale(Vector3Int v1, Vector3Int v2) { - return new Vector3Int(v1.horizontal * v2.horizontal, v1.vertical * v2.vertical, v1.depth * v2.depth); - } - - - public static Vector3Int operator *(Vector3Int v1, int d) { - Vector3Int v = new(v1.horizontal * d, v1.vertical * d, v1.depth * d); - return v; - } - - public static Vector3Int operator *(int d, Vector3Int v1) { - Vector3Int v = new(d * v1.horizontal, d * v1.vertical, d * v1.depth); - return v; - } - - public static Vector3Int operator /(Vector3Int v1, int d) { - Vector3Int v = new(v1.horizontal / d, v1.vertical / d, v1.depth / d); - return v; - } - - public bool Equals(Vector3Int v) => (horizontal == v.horizontal && vertical == v.vertical && depth == v.depth); - - public override bool Equals(object obj) { - if (!(obj is Vector3Int v)) - return false; - - return (horizontal == v.horizontal && vertical == v.vertical && depth == v.depth); - } - - public static bool operator ==(Vector3Int v1, Vector3Int v2) { - return (v1.horizontal == v2.horizontal && v1.vertical == v2.vertical && v1.depth == v2.depth); - } - - public static bool operator !=(Vector3Int v1, Vector3Int v2) { - return (v1.horizontal != v2.horizontal || v1.vertical != v2.vertical || v1.depth != v2.depth); - } - - public override int GetHashCode() { - return (horizontal, vertical, depth).GetHashCode(); - } - - /// @brief The distance between two vectors - /// @param v1 The first vector - /// @param v2 The second vector - /// @return The distance between the two vectors - public static float Distance(Vector3Int v1, Vector3Int v2) { - return (v2 - v1).magnitude; - } - - /// @brief The dot product of two vectors - /// @param v1 The first vector - /// @param v2 The second vector - /// @return The dot product of the two vectors - public static float Dot(Vector3Int v1, Vector3Int v2) { - return v1.horizontal * v2.horizontal + v1.vertical * v2.vertical + v1.depth * v2.depth; - } - - /// @brief The cross product of two vectors - /// @param v1 The first vector - /// @param v2 The second vector - /// @return The cross product of the two vectors - public static Vector3Int Cross(Vector3Int v1, Vector3Int v2) { - return new Vector3Int(v1.vertical * v2.depth - v1.depth * v2.vertical, v1.depth * v2.horizontal - v1.horizontal * v2.depth, - v1.horizontal * v2.vertical - v1.vertical * v2.horizontal); - - } - - /// @brief The angle between two vectors - /// @param v1 The first vector - /// @param v2 The second vector - /// @return The angle between the two vectors - /// @remark This reterns an unsigned angle which is the shortest distance - /// between the two vectors. Use Vector3::SignedAngle if a signed angle is - /// needed. - public static AngleFloat UnsignedAngle(Vector3Int v1, Vector3Int v2) { - float denominator = MathF.Sqrt(v1.sqrMagnitude * v2.sqrMagnitude); - if (denominator < Float.epsilon) - return AngleFloat.zero; - - float dot = Dot(v1, v2); - float fraction = dot / denominator; - if (float.IsNaN(fraction)) - return AngleFloat.Degrees( - fraction); // short cut to returning NaN universally - - float cdot = Float.Clamp(fraction, -1.0f, 1.0f); - float r = MathF.Acos(cdot); - return AngleFloat.Radians(r); - } - /// @brief The signed angle between two vectors - /// @param v1 The starting vector - /// @param v2 The ending vector - /// @param axis The axis to rotate around - /// @return The signed angle between the two vectors - public static AngleFloat SignedAngle(Vector3Int v1, Vector3Int v2, - Vector3Int axis) { - // angle in [0,180] - AngleFloat angle = UnsignedAngle(v1, v2); - - Vector3Int cross = Cross(v1, v2); - float b = Dot(axis, cross); - float signd = b < 0 ? -1.0F : (b > 0 ? 1.0F : 0.0F); - - // angle in [-179,180] - AngleFloat signed_angle = angle * signd; - - return signed_angle; - } - - } -} -//#endif \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/src/float16.cs b/NanoBrain/LinearAlgebra/src/float16.cs deleted file mode 100644 index 4b58cdd..0000000 --- a/NanoBrain/LinearAlgebra/src/float16.cs +++ /dev/null @@ -1,322 +0,0 @@ -using System; - -namespace 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); - _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; - } - - public static uint SingleToInt32Bits(float value) { - byte[] bytes = BitConverter.GetBytes(value); - if (BitConverter.IsLittleEndian) - Array.Reverse(bytes); // If the system is little-endian, reverse the byte order - return BitConverter.ToUInt32(bytes, 0); - } - - public ushort F32ToF16__(float f) { - uint t = BitConverter.ToUInt32(BitConverter.GetBytes(f), 0); - ushort man = (ushort)((t & 0x007FFFFF) >> 12); - int exp = (int)((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 = 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); - } - - //This function is faulty!!!! - ushort f32tof16(float f) { - //uint t = *(uint*)&f; - //uint t = (uint)BitConverter.SingleToInt32Bits(f); - uint t = 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; - ushort uexp = (ushort)exp; - if (sgn) - return (ushort)(0x8000 | uexp | man); - return (ushort)(uexp | man); - } - - // -- END OF FILE -- - } - -} \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/test/AngleTest.cs b/NanoBrain/LinearAlgebra/test/AngleTest.cs deleted file mode 100644 index 8362d82..0000000 --- a/NanoBrain/LinearAlgebra/test/AngleTest.cs +++ /dev/null @@ -1,501 +0,0 @@ -#if !UNITY_5_6_OR_NEWER -using System; -using System.Formats.Asn1; -using NUnit.Framework; - -namespace LinearAlgebra.Test { - public class AngleTests { - [SetUp] - public void Setup() { - } - - [Test] - public void Construct() { - // Degrees - float angle = 0.0f; - AngleFloat a = AngleFloat.Degrees(angle); - Assert.AreEqual(angle, a.inDegrees); - - angle = -180.0f; - a = AngleFloat.Degrees(angle); - Assert.AreEqual(angle, a.inDegrees); - - angle = 270.0f; - a = AngleFloat.Degrees(angle); - Assert.AreEqual(-90, a.inDegrees); - - angle = -270.0f; - a = AngleFloat.Degrees(angle); - Assert.AreEqual(90, a.inDegrees); - - // Radians - angle = 0.0f; - a = AngleFloat.Radians(angle); - Assert.AreEqual(angle, a.inRadians); - - angle = (float)-Math.PI; - a = AngleFloat.Radians(angle); - Assert.AreEqual(angle, a.inRadians); - - angle = (float)Math.PI * 1.5f; - a = AngleFloat.Radians(angle); - Assert.AreEqual(-Math.PI * 0.5f, a.inRadians, 1.0E-05F); - - // Revolutions - angle = 0.0f; - a = AngleFloat.Revolutions(angle); - Assert.AreEqual(angle, a.inRevolutions); - - angle = -0.5f; - a = AngleFloat.Revolutions(angle); - Assert.AreEqual(angle, a.inRevolutions); - - angle = 0.75f; - a = AngleFloat.Revolutions(angle); - Assert.AreEqual(-0.25f, a.inRevolutions); - - } - - [Test] - public void Revolutions() { - AngleFloat a; - - // Test zero - a = AngleFloat.Revolutions(0.0f); - Assert.AreEqual(0.0f, a.inRevolutions); - - // Test positive values within range - a = AngleFloat.Revolutions(0.25f); - Assert.AreEqual(0.25f, a.inRevolutions); - - a = AngleFloat.Revolutions(0.5f); - Assert.AreEqual(-0.5f, a.inRevolutions); - - // Test negative values within range - a = AngleFloat.Revolutions(-0.25f); - Assert.AreEqual(-0.25f, a.inRevolutions); - - a = AngleFloat.Revolutions(-0.5f); - Assert.AreEqual(-0.5f, a.inRevolutions); - - // Test values outside range (positive) - a = AngleFloat.Revolutions(1.0f); - Assert.AreEqual(0.0f, a.inRevolutions); - - a = AngleFloat.Revolutions(1.25f); - Assert.AreEqual(0.25f, a.inRevolutions); - - a = AngleFloat.Revolutions(1.75f); - Assert.AreEqual(-0.25f, a.inRevolutions); - - // Test values outside range (negative) - a = AngleFloat.Revolutions(-1.0f); - Assert.AreEqual(0.0f, a.inRevolutions); - - a = AngleFloat.Revolutions(-1.25f); - Assert.AreEqual(-0.25f, a.inRevolutions); - - a = AngleFloat.Revolutions(-1.75f); - Assert.AreEqual(0.25f, a.inRevolutions); - - // Test infinity - a = AngleFloat.Revolutions(float.PositiveInfinity); - Assert.AreEqual(float.PositiveInfinity, a.inRevolutions); - - a = AngleFloat.Revolutions(float.NegativeInfinity); - Assert.AreEqual(float.NegativeInfinity, a.inRevolutions); - } - - [Test] - public void Equality() { - // Test equality operator - Assert.IsTrue(AngleFloat.Degrees(90) == AngleFloat.Degrees(90), "90 == 90"); - Assert.IsFalse(AngleFloat.Degrees(90) == AngleFloat.Degrees(45), "90 == 45"); - Assert.IsTrue(AngleFloat.Degrees(0) == AngleFloat.Degrees(0), "0 == 0"); - Assert.IsTrue(AngleFloat.Degrees(-180) == AngleFloat.Degrees(-180), "-180 == -180"); - - // Test inequality operator - Assert.IsTrue(AngleFloat.Degrees(90) != AngleFloat.Degrees(45), "90 != 45"); - Assert.IsFalse(AngleFloat.Degrees(90) != AngleFloat.Degrees(90), "90 != 90"); - Assert.IsTrue(AngleFloat.Degrees(0) != AngleFloat.Degrees(1), "0 != 1"); - - // Test greater than operator - Assert.IsTrue(AngleFloat.Degrees(90) > AngleFloat.Degrees(45), "90 > 45"); - Assert.IsFalse(AngleFloat.Degrees(45) > AngleFloat.Degrees(90), "45 > 90"); - Assert.IsFalse(AngleFloat.Degrees(90) > AngleFloat.Degrees(90), "90 > 90"); - - // Test greater than or equal operator - Assert.IsTrue(AngleFloat.Degrees(90) >= AngleFloat.Degrees(45), "90 >= 45"); - Assert.IsTrue(AngleFloat.Degrees(90) >= AngleFloat.Degrees(90), "90 >= 90"); - Assert.IsFalse(AngleFloat.Degrees(45) >= AngleFloat.Degrees(90), "45 >= 90"); - - // Test less than operator - Assert.IsTrue(AngleFloat.Degrees(45) < AngleFloat.Degrees(90), "45 < 90"); - Assert.IsFalse(AngleFloat.Degrees(90) < AngleFloat.Degrees(45), "90 < 45"); - Assert.IsFalse(AngleFloat.Degrees(90) < AngleFloat.Degrees(90), "90 < 90"); - - // Test less than or equal operator - Assert.IsTrue(AngleFloat.Degrees(45) <= AngleFloat.Degrees(90), "45 <= 90"); - Assert.IsTrue(AngleFloat.Degrees(90) <= AngleFloat.Degrees(90), "90 <= 90"); - Assert.IsFalse(AngleFloat.Degrees(90) <= AngleFloat.Degrees(45), "90 <= 45"); - } - - // [Test] - // public void Normalize() { - // float r = 0; - - // r = Angle.Normalize(90); - // Assert.AreEqual(r, 90, "Normalize 90"); - - // r = Angle.Normalize(-90); - // Assert.AreEqual(r, -90, "Normalize -90"); - - // r = Angle.Normalize(270); - // Assert.AreEqual(r, -90, "Normalize 270"); - - // r = Angle.Normalize(270 + 360); - // Assert.AreEqual(r, -90, "Normalize 270+360"); - - // r = Angle.Normalize(-270); - // Assert.AreEqual(r, 90, "Normalize -270"); - - // r = Angle.Normalize(-270 - 360); - // Assert.AreEqual(r, 90, "Normalize -270-360"); - - // r = Angle.Normalize(0); - // Assert.AreEqual(r, 0, "Normalize 0"); - - // r = Angle.Normalize(float.PositiveInfinity); - // Assert.AreEqual(r, float.PositiveInfinity, "Normalize INFINITY"); - - // r = Angle.Normalize(float.NegativeInfinity); - // Assert.AreEqual(r, float.NegativeInfinity, "Normalize INFINITY"); - // } - - [Test] - public void Clamp() { - float r = 0; - - r = AngleFloat.Clamp(AngleFloat.Degrees(1), AngleFloat.Degrees(0), AngleFloat.Degrees(2)); - Assert.AreEqual(1, r, "Clamp 1 0 2"); - - r = AngleFloat.Clamp(AngleFloat.Degrees(-1), AngleFloat.Degrees(0), AngleFloat.Degrees(2)); - Assert.AreEqual(0, r, "Clamp -1 0 2"); - - r = AngleFloat.Clamp(AngleFloat.Degrees(3), AngleFloat.Degrees(0), AngleFloat.Degrees(2)); - Assert.AreEqual(2, r, "Clamp 3 0 2"); - - r = AngleFloat.Clamp(AngleFloat.Degrees(1), AngleFloat.Degrees(0), AngleFloat.Degrees(0)); - Assert.AreEqual(0, r, "Clamp 1 0 0"); - - r = AngleFloat.Clamp(AngleFloat.Degrees(0), AngleFloat.Degrees(0), AngleFloat.Degrees(0)); - Assert.AreEqual(0, r, "Clamp 0 0 0"); - - r = AngleFloat.Clamp(AngleFloat.Degrees(0), AngleFloat.Degrees(1), AngleFloat.Degrees(-1)); - Assert.AreEqual(1, r, "Clamp 0 1 -1"); - - r = AngleFloat.Clamp(AngleFloat.Degrees(1), AngleFloat.Degrees(0), AngleFloat.Degrees(float.PositiveInfinity)); - Assert.AreEqual(1, r, "Clamp 1 0 INFINITY"); - - r = AngleFloat.Clamp(AngleFloat.Degrees(1), AngleFloat.Degrees(float.NegativeInfinity), AngleFloat.Degrees(1)); - Assert.AreEqual(1, r, "Clamp 1 -INFINITY 1"); - } - - [Test] - public void Cos() { - // Test zero - Assert.AreEqual(1.0f, AngleFloat.Cos(AngleFloat.Degrees(0)), 1.0E-05F, "Cos(0°)"); - - // Test 90 degrees - Assert.AreEqual(0.0f, AngleFloat.Cos(AngleFloat.Degrees(90)), 1.0E-05F, "Cos(90°)"); - - // Test 180 degrees - Assert.AreEqual(-1.0f, AngleFloat.Cos(AngleFloat.Degrees(180)), 1.0E-05F, "Cos(180°)"); - - // Test 270 degrees - Assert.AreEqual(0.0f, AngleFloat.Cos(AngleFloat.Degrees(270)), 1.0E-05F, "Cos(270°)"); - - // Test 45 degrees - Assert.AreEqual(MathF.Sqrt(2) / 2, AngleFloat.Cos(AngleFloat.Degrees(45)), 1.0E-05F, "Cos(45°)"); - - // Test negative angle - Assert.AreEqual(1.0f, AngleFloat.Cos(AngleFloat.Degrees(-360)), 1.0E-05F, "Cos(-360°)"); - - // Test using radians - Assert.AreEqual(1.0f, AngleFloat.Cos(AngleFloat.Radians(0)), 1.0E-05F, "Cos(0 rad)"); - Assert.AreEqual(0.0f, AngleFloat.Cos(AngleFloat.Radians((float)Math.PI / 2)), 1.0E-05F, "Cos(π/2)"); - Assert.AreEqual(-1.0f, AngleFloat.Cos(AngleFloat.Radians((float)Math.PI)), 1.0E-05F, "Cos(π)"); - } - - [Test] - public void Sin() { - // Test zero - Assert.AreEqual(0.0f, AngleFloat.Sin(AngleFloat.Degrees(0)), 1.0E-05F, "Sin(0°)"); - - // Test 90 degrees - Assert.AreEqual(1.0f, AngleFloat.Sin(AngleFloat.Degrees(90)), 1.0E-05F, "Sin(90°)"); - - // Test 180 degrees - Assert.AreEqual(0.0f, AngleFloat.Sin(AngleFloat.Degrees(180)), 1.0E-05F, "Sin(180°)"); - - // Test 270 degrees - Assert.AreEqual(-1.0f, AngleFloat.Sin(AngleFloat.Degrees(270)), 1.0E-05F, "Sin(270°)"); - - // Test 45 degrees - Assert.AreEqual(MathF.Sqrt(2) / 2, AngleFloat.Sin(AngleFloat.Degrees(45)), 1.0E-05F, "Sin(45°)"); - - // Test negative angle - Assert.AreEqual(0.0f, AngleFloat.Sin(AngleFloat.Degrees(-360)), 1.0E-05F, "Sin(-360°)"); - - // Test using radians - Assert.AreEqual(0.0f, AngleFloat.Sin(AngleFloat.Radians(0)), 1.0E-05F, "Sin(0 rad)"); - Assert.AreEqual(1.0f, AngleFloat.Sin(AngleFloat.Radians((float)Math.PI / 2)), 1.0E-05F, "Sin(π/2)"); - Assert.AreEqual(0.0f, AngleFloat.Sin(AngleFloat.Radians((float)Math.PI)), 1.0E-05F, "Sin(π)"); - } - - [Test] - public void Tan() { - // Test zero - Assert.AreEqual(0.0f, AngleFloat.Tan(AngleFloat.Degrees(0)), 1.0E-05F, "Tan(0°)"); - - // Test 45 degrees - Assert.AreEqual(1.0f, AngleFloat.Tan(AngleFloat.Degrees(45)), 1.0E-05F, "Tan(45°)"); - - // Test -45 degrees - Assert.AreEqual(-1.0f, AngleFloat.Tan(AngleFloat.Degrees(-45)), 1.0E-05F, "Tan(-45°)"); - - // Test using radians - Assert.AreEqual(0.0f, AngleFloat.Tan(AngleFloat.Radians(0)), 1.0E-05F, "Tan(0 rad)"); - Assert.AreEqual(1.0f, AngleFloat.Tan(AngleFloat.Radians((float)Math.PI / 4)), 1.0E-05F, "Tan(π/4)"); - } - - [Test] - public void Acos() { - // Test 1 (0 degrees) - Assert.AreEqual(0.0f, AngleFloat.Acos(1.0f).inRadians, 1.0E-05F, "Acos(1)"); - - // Test 0 (90 degrees or π/2 radians) - Assert.AreEqual((float)Math.PI / 2, AngleFloat.Acos(0.0f).inRadians, 1.0E-05F, "Acos(0)"); - - // Test -1 (-180 degrees or π radians) - Assert.AreEqual((float)-Math.PI, AngleFloat.Acos(-1.0f).inRadians, 1.0E-05F, "Acos(-1)"); - - // Test 0.5 (60 degrees or π/3 radians) - Assert.AreEqual((float)Math.PI / 3, AngleFloat.Acos(0.5f).inRadians, 1.0E-05F, "Acos(0.5)"); - - // Test sqrt(2)/2 (45 degrees or π/4 radians) - Assert.AreEqual((float)Math.PI / 4, AngleFloat.Acos(MathF.Sqrt(2) / 2).inRadians, 1.0E-05F, "Acos(√2/2)"); - } - - [Test] - public void Asin() { - // Test 0 (0 degrees) - Assert.AreEqual(0.0f, AngleFloat.Asin(0.0f).inRadians, 1.0E-05F, "Asin(0)"); - - // Test 1 (90 degrees or π/2 radians) - Assert.AreEqual((float)Math.PI / 2, AngleFloat.Asin(1.0f).inRadians, 1.0E-05F, "Asin(1)"); - - // Test -1 (-90 degrees or -π/2 radians) - Assert.AreEqual(-(float)Math.PI / 2, AngleFloat.Asin(-1.0f).inRadians, 1.0E-05F, "Asin(-1)"); - - // Test 0.5 (30 degrees or π/6 radians) - Assert.AreEqual((float)Math.PI / 6, AngleFloat.Asin(0.5f).inRadians, 1.0E-05F, "Asin(0.5)"); - - // Test sqrt(2)/2 (45 degrees or π/4 radians) - Assert.AreEqual((float)Math.PI / 4, AngleFloat.Asin(MathF.Sqrt(2) / 2).inRadians, 1.0E-05F, "Asin(√2/2)"); - } - - - [Test] - public void Atan() { - // Test zero - Assert.AreEqual(0.0f, AngleFloat.Atan(0.0f).inRadians, 1.0E-05F, "Atan(0)"); - - // Test 1 (45 degrees or π/4 radians) - Assert.AreEqual((float)Math.PI / 4, AngleFloat.Atan(1.0f).inRadians, 1.0E-05F, "Atan(1)"); - - // Test -1 (-45 degrees or -π/4 radians) - Assert.AreEqual(-(float)Math.PI / 4, AngleFloat.Atan(-1.0f).inRadians, 1.0E-05F, "Atan(-1)"); - - // Test sqrt(3) (60 degrees or π/3 radians) - Assert.AreEqual((float)Math.PI / 3, AngleFloat.Atan(MathF.Sqrt(3)).inRadians, 1.0E-05F, "Atan(√3)"); - - // Test 1/sqrt(3) (30 degrees or π/6 radians) - Assert.AreEqual((float)Math.PI / 6, AngleFloat.Atan(1.0f / MathF.Sqrt(3)).inRadians, 1.0E-05F, "Atan(1/√3)"); - - // Test positive infinity - Assert.AreEqual((float)Math.PI / 2, AngleFloat.Atan(float.PositiveInfinity).inRadians, 1.0E-05F, "Atan(+∞)"); - - // Test negative infinity - Assert.AreEqual(-(float)Math.PI / 2, AngleFloat.Atan(float.NegativeInfinity).inRadians, 1.0E-05F, "Atan(-∞)"); - } - - [Test] - public void Atan2() { - // Test basic quadrant I - Assert.AreEqual((float)Math.PI / 4, AngleFloat.Atan2(1.0f, 1.0f).inRadians, 1.0E-05F, "Atan2(1, 1)"); - - // Test quadrant II - Assert.AreEqual(3 * (float)Math.PI / 4, AngleFloat.Atan2(1.0f, -1.0f).inRadians, 1.0E-05F, "Atan2(1, -1)"); - - // Test quadrant III - Assert.AreEqual(-(float)Math.PI * 0.75f, AngleFloat.Atan2(-1.0f, -1.0f).inRadians, 1.0E-05F, "Atan2(-1, -1)"); - - // Test quadrant IV - Assert.AreEqual(-(float)Math.PI / 4, AngleFloat.Atan2(-1.0f, 1.0f).inRadians, 1.0E-05F, "Atan2(-1, 1)"); - - // Test positive x-axis - Assert.AreEqual(0.0f, AngleFloat.Atan2(0.0f, 1.0f).inRadians, 1.0E-05F, "Atan2(0, 1)"); - - // Test positive y-axis - Assert.AreEqual((float)Math.PI / 2, AngleFloat.Atan2(1.0f, 0.0f).inRadians, 1.0E-05F, "Atan2(1, 0)"); - - // Test negative y-axis - Assert.AreEqual(-(float)Math.PI / 2, AngleFloat.Atan2(-1.0f, 0.0f).inRadians, 1.0E-05F, "Atan2(-1, 0)"); - - // Test origin - Assert.AreEqual(0.0f, AngleFloat.Atan2(0.0f, 0.0f).inRadians, 1.0E-05F, "Atan2(0, 0)"); - - // Test with different magnitudes - Assert.AreEqual((float)Math.PI / 3, AngleFloat.Atan2(MathF.Sqrt(3), 1.0f).inRadians, 1.0E-05F, "Atan2(√3, 1)"); - - // Test negative x-axis - Assert.AreEqual((float)-Math.PI, AngleFloat.Atan2(0.0f, -1.0f).inRadians, 1.0E-05F, "Atan2(0, -1)"); - } - - [Test] - public void Multiplication() { - AngleFloat r = AngleFloat.zero; - - // Angle * float - r = AngleFloat.Degrees(90) * 2; - Assert.AreEqual(-180, r.inDegrees, "Multiply 90 * 2"); - - r = AngleFloat.Degrees(45) * 0.5f; - Assert.AreEqual(22.5f, r.inDegrees, "Multiply 45 * 0.5"); - - r = AngleFloat.Degrees(90) * 0; - Assert.AreEqual(0, r.inDegrees, "Multiply 90 * 0"); - - r = AngleFloat.Degrees(-90) * 2; - Assert.AreEqual(-180, r.inDegrees, "Multiply -90 * 2"); - - r = AngleFloat.Degrees(270) * 2; - Assert.AreEqual(-180, r.inDegrees, "Multiply 270 * 2 (normalized)"); - - // float * Angle - r = 2 * AngleFloat.Degrees(90); - Assert.AreEqual(-180, r.inDegrees, "Multiply 2 * 90"); - - r = 0.5f * AngleFloat.Degrees(45); - Assert.AreEqual(22.5, r.inDegrees, "Multiply 0.5 * 45"); - - r = 0 * AngleFloat.Degrees(90); - Assert.AreEqual(0, r.inDegrees, "Multiply 0 * 90"); - - r = 2 * AngleFloat.Degrees(-90); - Assert.AreEqual(-180, r.inDegrees, "Multiply 2 * -90"); - - // Negative factor - r = AngleFloat.Degrees(90) * -1; - Assert.AreEqual(-90, r.inDegrees, "Multiply 90 * -1"); - - r = -1 * AngleFloat.Degrees(90); - Assert.AreEqual(-90, r.inDegrees, "Multiply -1 * 90"); - } - - [Test] - public void MoveTowards() { - AngleFloat r = AngleFloat.zero; - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(90), 30); - Assert.AreEqual(30, r.inDegrees, "MoveTowards 0 90 30"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(90), 90); - Assert.AreEqual(90, r.inDegrees, "MoveTowards 0 90 90"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(90), 180); - Assert.AreEqual(90, r.inDegrees, "MoveTowards 0 90 180"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(90), 270); - Assert.AreEqual(90, r.inDegrees, "MoveTowrads 0 90 270"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(90), -30); - Assert.AreEqual(0, r.inDegrees, "MoveTowards 0 90 -30"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(-90), -30); - Assert.AreEqual(0, r.inDegrees, "MoveTowards 0 -90 -30"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(-90), -90); - Assert.AreEqual(0, r.inDegrees, "MoveTowards 0 -90 -90"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(-90), -180); - Assert.AreEqual(0, r.inDegrees, "MoveTowards 0 -90 -180"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(-90), -270); - Assert.AreEqual(0, r.inDegrees, "MoveTowrads 0 -90 -270"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(90), 0); - Assert.AreEqual(0, r.inDegrees, "MoveTowards 0 90 0"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(0), 0); - Assert.AreEqual(0, r.inDegrees, "MoveTowards 0 0 0"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(0), 30); - Assert.AreEqual(0, r.inDegrees, "MoveTowrads 0 0 30"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(90), float.PositiveInfinity); - Assert.AreEqual(90, r.inDegrees, "MoveTowards 0 90 INFINITY"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(float.PositiveInfinity), 30); - Assert.AreEqual(30, r.inDegrees, "MoveTowrads 0 INFINITY 30"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(-90), float.NegativeInfinity); - Assert.AreEqual(0, r.inDegrees, "MoveTowards 0 -90 -INFINITY"); - - r = AngleFloat.MoveTowards(AngleFloat.Degrees(0), AngleFloat.Degrees(float.NegativeInfinity), -30); - Assert.AreEqual(0, r.inDegrees, "MoveTowrads 0 -INFINITY -30"); - - } - - [Test] - public void Difference() { - float r = 0; - - r = Angles.Difference(0, 90); - Assert.AreEqual(90, r, "Difference 0 90"); - - r = Angles.Difference(0, -90); - Assert.AreEqual(-90, r, "Difference 0 -90"); - - r = Angles.Difference(0, 270); - Assert.AreEqual(-90, r, "Difference 0 270"); - - r = Angles.Difference(0, -270); - Assert.AreEqual(90, r, "Difference 0 -270"); - - r = Angles.Difference(90, 0); - Assert.AreEqual(-90, r, "Difference 90 0"); - - r = Angles.Difference(-90, 0); - Assert.AreEqual(90, r, "Difference -90 0"); - - r = Angles.Difference(0, 0); - Assert.AreEqual(0, r, "Difference 0 0"); - - r = Angles.Difference(90, 90); - Assert.AreEqual(0, r, "Difference 90 90"); - - r = Angles.Difference(0, float.PositiveInfinity); - Assert.AreEqual(float.PositiveInfinity, r, "Difference 0 INFINITY"); - - r = Angles.Difference(0, float.NegativeInfinity); - Assert.AreEqual(float.NegativeInfinity, r, "Difference 0 -INFINITY"); - - r = Angles.Difference(float.NegativeInfinity, float.PositiveInfinity); - Assert.AreEqual(float.PositiveInfinity, r, "Difference -INFINITY INFINITY"); - } - } - -} -#endif diff --git a/NanoBrain/LinearAlgebra/test/DirectionTest.cs b/NanoBrain/LinearAlgebra/test/DirectionTest.cs deleted file mode 100644 index 0eb9882..0000000 --- a/NanoBrain/LinearAlgebra/test/DirectionTest.cs +++ /dev/null @@ -1,226 +0,0 @@ -#if !UNITY_5_6_OR_NEWER -using System; -using NUnit.Framework; - -namespace LinearAlgebra.Test { - public class DirectionTest { - - [Test] - public void RadiansForward() { - Direction d = Direction.Radians(0, 0); - Assert.AreEqual(0, d.horizontal.inDegrees, 0.0001f); - Assert.AreEqual(0, d.vertical.inDegrees, 0.0001f); - } - - [Test] - public void RadiansUp() { - Direction d = Direction.Radians(0, (float)Math.PI / 2); - Assert.AreEqual(0, d.horizontal.inDegrees, 0.0001f); - Assert.AreEqual(90, d.vertical.inDegrees, 0.0001f); - } - - [Test] - public void RadiansDown() { - Direction d = Direction.Radians(0, -(float)Math.PI / 2); - Assert.AreEqual(0, d.horizontal.inDegrees, 0.0001f); - Assert.AreEqual(-90, d.vertical.inDegrees, 0.0001f); - } - - [Test] - public void RadiansArbitrary() { - Direction d = Direction.Radians((float)Math.PI / 4, (float)Math.PI / 6); - Assert.AreEqual(45, d.horizontal.inDegrees, 0.0001f); - Assert.AreEqual(30, d.vertical.inDegrees, 0.0001f); - } - - [Test] - public void DegreesNormalize1() { - Direction d = Direction.Degrees(112, 91); - Assert.AreEqual(-68, d.horizontal.inDegrees, 0.0001f); - Assert.AreEqual(89, d.vertical.inDegrees, 0.0001f); - } - - [Test] - public void RadiansEquivalentToDegreesConversion() { - Direction d1 = Direction.Radians((float)Math.PI / 3, (float)Math.PI / 4); - Direction d2 = Direction.Degrees(60, 45); - Assert.AreEqual(d1.horizontal.inDegrees, d2.horizontal.inDegrees, 0.0001f); - Assert.AreEqual(d1.vertical.inDegrees, d2.vertical.inDegrees, 0.0001f); - } - - [Test] - public void ToVector3Forward() { - Direction d = Direction.forward; - Vector3Float v = d.ToVector3(); - Assert.AreEqual(0, v.horizontal, 0.0001f); - Assert.AreEqual(0, v.vertical, 0.0001f); - Assert.AreEqual(1, v.depth, 0.0001f); - } - - [Test] - public void ToVector3Up() { - Direction d = Direction.up; - Vector3Float v = d.ToVector3(); - Assert.AreEqual(0, v.horizontal, 0.0001f); - Assert.AreEqual(1, v.vertical, 0.0001f); - Assert.AreEqual(0, v.depth, 0.0001f); - } - - [Test] - public void ToVector3Down() { - Direction d = Direction.down; - Vector3Float v = d.ToVector3(); - Assert.AreEqual(0, v.horizontal, 0.0001f); - Assert.AreEqual(-1, v.vertical, 0.0001f); - Assert.AreEqual(0, v.depth, 0.0001f); - } - - [Test] - public void ToVector3Left() { - Direction d = Direction.left; - Vector3Float v = d.ToVector3(); - Assert.AreEqual(-1, v.horizontal, 0.0001f); - Assert.AreEqual(0, v.vertical, 0.0001f); - Assert.AreEqual(0, v.depth, 0.0001f); - } - - [Test] - public void FromVector3Forward() { - Vector3Float v = new(0, 0, 1); - Direction d = Direction.FromVector3(v); - Assert.AreEqual(0, d.horizontal.inDegrees, 0.0001f); - Assert.AreEqual(0, d.vertical.inDegrees, 0.0001f); - } - - [Test] - public void ToVector3AndBack() { - Direction d1 = Direction.Degrees(45, 30); - Vector3Float v = d1.ToVector3(); - Direction d2 = Direction.FromVector3(v); - Assert.AreEqual(d1.horizontal.inDegrees, d2.horizontal.inDegrees, 0.0001f); - Assert.AreEqual(d1.vertical.inDegrees, d2.vertical.inDegrees, 0.0001f); - } - - [Test] - public void ToVector3AndBack2() { - 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); - Assert.AreEqual(d1.vertical.inDegrees, d2.vertical.inDegrees, 0.0001f); - } - - [Test] - public void Compare() { - Direction d1 = Direction.Degrees(45, 135); - Direction d2 = new(AngleFloat.Degrees(45), AngleFloat.Degrees(135)); - bool r; - r = d1 == d2; - Assert.True(r); - Assert.AreEqual(d1, d2); - } - - [Test] - public void NotEqualWithDifferentHorizontal() { - Direction d1 = Direction.Degrees(45, 30); - Direction d2 = Direction.Degrees(90, 30); - Assert.True(d1 != d2); - } - - [Test] - public void NotEqualWithDifferentVertical() { - Direction d1 = Direction.Degrees(45, 30); - Direction d2 = Direction.Degrees(45, 60); - Assert.True(d1 != d2); - } - - [Test] - public void NotEqualWithDifferentBoth() { - Direction d1 = Direction.Degrees(45, 30); - Direction d2 = Direction.Degrees(90, 60); - Assert.True(d1 != d2); - } - - [Test] - public void NotEqualWithSameValues() { - Direction d1 = Direction.Degrees(45, 30); - Direction d2 = Direction.Degrees(45, 30); - Assert.False(d1 != d2); - } - - - [Test] - public void EqualsWithSameValues() { - Direction d1 = Direction.Degrees(45, 30); - Direction d2 = Direction.Degrees(45, 30); - Assert.True(d1.Equals(d2)); - } - - [Test] - public void EqualsWithDifferentHorizontal() { - Direction d1 = Direction.Degrees(45, 30); - Direction d2 = Direction.Degrees(90, 30); - Assert.False(d1.Equals(d2)); - } - - [Test] - public void EqualsWithDifferentVertical() { - Direction d1 = Direction.Degrees(45, 30); - Direction d2 = Direction.Degrees(45, 60); - Assert.False(d1.Equals(d2)); - } - - [Test] - public void EqualsWithDifferentBoth() { - Direction d1 = Direction.Degrees(45, 30); - Direction d2 = Direction.Degrees(90, 60); - Assert.False(d1.Equals(d2)); - } - - [Test] - public void EqualsWithNonDirectionObject() { - Direction d = Direction.Degrees(45, 30); - Assert.False(d.Equals("not a direction")); - } - - [Test] - public void EqualsWithNull() { - Direction d = Direction.Degrees(45, 30); - Assert.False(d.Equals(null)); - } - - [Test] - public void EqualsWithZeros() { - Direction d1 = Direction.forward; - Direction d2 = Direction.Degrees(0, 0); - Assert.True(d1.Equals(d2)); - } - - [Test] - public void HashCode() { - Direction d1 = Direction.Degrees(45, 30); - Direction d2 = Direction.Degrees(45, 30); - Assert.AreEqual(d1.GetHashCode(), d2.GetHashCode()); - - d1 = Direction.Degrees(45, 30); - d2 = Direction.Degrees(90, 30); - Assert.AreNotEqual(d1.GetHashCode(), d2.GetHashCode()); - - d1 = Direction.Degrees(45, 30); - d2 = Direction.Degrees(45, 60); - Assert.AreNotEqual(d1.GetHashCode(), d2.GetHashCode()); - - Direction d = Direction.Degrees(45, 30); - int hash1 = d.GetHashCode(); - int hash2 = d.GetHashCode(); - Assert.AreEqual(hash1, hash2); - - d1 = Direction.forward; - d2 = Direction.Degrees(0, 0); - Assert.AreEqual(d1.GetHashCode(), d2.GetHashCode()); - } - - }; -} -#endif - diff --git a/NanoBrain/LinearAlgebra/test/LinearAlgebra_Test.csproj b/NanoBrain/LinearAlgebra/test/LinearAlgebra_Test.csproj deleted file mode 100644 index 5b48e60..0000000 --- a/NanoBrain/LinearAlgebra/test/LinearAlgebra_Test.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net8.0 - false - true - - - - - - - - - - - - - diff --git a/NanoBrain/LinearAlgebra/test/QuaternionTest.cs b/NanoBrain/LinearAlgebra/test/QuaternionTest.cs deleted file mode 100644 index 9dd5a96..0000000 --- a/NanoBrain/LinearAlgebra/test/QuaternionTest.cs +++ /dev/null @@ -1,185 +0,0 @@ -#if !UNITY_5_6_OR_NEWER -using NUnit.Framework; - -namespace LinearAlgebra.Test { - - public class QuaternionTest { - - [SetUp] - public void Setup() { - } - - [Test] - public void Normalize() { - Quaternion q1 = new(0, 0, 0, 1); - Quaternion r = Quaternion.identity; - - r = q1.normalized; - Assert.AreEqual(r, q1, "q.normalized 0 0 0 1"); - - r = Quaternion.Normalize(q1); - Assert.AreEqual(r, q1, "q.normalized 0 0 0 1"); - } - - [Test] - public void ToAngles() { - Quaternion q1 = new(0, 0, 0, 1); - Vector3Float v = Vector3Float.zero; - - v = Quaternion.ToAngles(q1); - Assert.AreEqual(v, new Vector3Float(0, 0, 0), "ToAngles 0 0 0 1"); - - q1 = new(1, 0, 0, 0); - v = Quaternion.ToAngles(q1); - Assert.AreEqual(0, v.horizontal, "1 0 0 0 H"); - Assert.AreEqual(180, v.vertical, "1 0 0 0 V"); - Assert.AreEqual(180, v.depth, "1 0 0 0 D"); - - } - - [Test] - public void Multiplication() { - Quaternion q1 = new(0, 0, 0, 1); - Quaternion q2 = new(1, 0, 0, 0); - Quaternion r; - - r = q1 * q2; - Assert.AreEqual(r, new Quaternion(1, 0, 0, 0), "0 0 0 1 * 1 0 0 0 "); - } - - [Test] - public void MultiplicationVector() { - Quaternion q1 = new(0, 0, 0, 1); - Vector3Float v1 = new(0, 1, 0); - Vector3Float r; - - r = q1 * v1; - Assert.AreEqual(r, new Vector3Float(0, 1, 0), "0 0 0 1 * Vector 0 1 0"); - - q1 = new(1, 0, 0, 0); - r = q1 * v1; - Assert.AreEqual(r, new Vector3Float(0, -1, 0), "1 0 0 0 * Vector 0 1 0"); - } - - [Test] - public void Equality() { - Quaternion q1 = new(0, 0, 0, 1); - Quaternion q2 = new(1, 0, 0, 0); - Assert.AreNotEqual(q1, q2, "0 0 0 1 == 1 0 0 0"); - - q2 = new(0, 0, 0, 1); - Assert.AreEqual(q1, q2, "0 0 0 1 == 1 0 0 0"); - } - - [Test, Ignore("ToDo")] - public void Inverse() { } - - [Test, Ignore("ToDo")] - public void LookRotation() { } - - [Test, Ignore("ToDo")] - public void FromToRotation() { } - - [Test, Ignore("ToDo")] - public void RotateTowards() { } - - [Test, Ignore("ToDo")] - public void AngleAxis() { } - - [Test, Ignore("ToDo")] - public void Angle() { } - - [Test, Ignore("ToDo")] - public void Slerp() { } - - [Test, Ignore("ToDo")] - public void SlerpUnclamped() { } - - [Test] - public void Euler() { - Vector3Float v1 = new(0, 0, 0); - Quaternion q; - - q = Quaternion.Euler(v1); - Assert.AreEqual(q, Quaternion.identity, "Euler Vector 0 0 0"); - - q = Quaternion.Euler(0, 0, 0); - Assert.AreEqual(q, Quaternion.identity, "Euler 0 0 0"); - - v1 = new(90, 90, -90); - q = Quaternion.Euler(v1); - Assert.AreEqual(q, new Quaternion(0, 0.707106709F, -0.707106709F, 0), "Euler Vector 90 90 -90"); - - q = Quaternion.Euler(90, 90, -90); - Assert.AreEqual(q, new Quaternion(0, 0.707106709F, -0.707106709F, 0), "Euler 90 90 -90"); - } - - [Test] - public void EulerToAngles() { - Vector3Float v; - Quaternion q; - Quaternion r; - - //v = new(0, 0, 0); - q = Quaternion.Euler(0, 0 , 0); - v = Quaternion.ToAngles(q); - r = Quaternion.Euler(v); - Assert.AreEqual(0, Quaternion.UnsignedAngle(q, r), 10e-2f, "0 0 0"); - - q = Quaternion.Euler(-45, -30, -15); - v = Quaternion.ToAngles(q); - r = Quaternion.Euler(v); - Assert.AreEqual(0, Quaternion.UnsignedAngle(q, r), 10e-2f, "-45, -30, -15"); - - // Gimball lock - // q = Quaternion.Euler(90, 90, -90); - // v = Quaternion.ToAngles(q); - // r = Quaternion.Euler(v); - // Assert.AreEqual(0, Quaternion.UnsignedAngle(q, r), 10e-2f, "0 0 0"); - - } - - [Test] - public void GetAngleAround() { - Vector3Float v1 = new(0, 1, 0); - Quaternion q1 = new(0, 0, 0, 1); - - float f = Quaternion.GetAngleAround(v1, q1); - Assert.AreEqual(f, 0, "GetAngleAround 0 1 0 , 0 0 0 1"); - - q1 = new(0, 0.707106709F, -0.707106709F, 0); - f = Quaternion.GetAngleAround(v1, q1); - Assert.AreEqual(f, 180, "GetAngleAround 0 1 0 , 0 0.7 -0.7 0"); - - v1 = new(0, 0, 0); - f = Quaternion.GetAngleAround(v1, q1); - Assert.IsTrue(float.IsNaN(f), "GetAngleAround 0 0 0 , 0 0.7 -0.7 0"); - } - - [Test] - public void GetRotationAround() { - Vector3Float v1 = new(0, 1, 0); - Quaternion q1 = new(0, 0, 0, 1); - - Quaternion q = Quaternion.GetRotationAround(v1, q1); - Assert.AreEqual(q, new Quaternion(0, 0, 0, 1), "GetRotationAround 0 1 0 , 0 0 0 1"); - - q1 = new(0, 0.707106709F, -0.707106709F, 0); - q = Quaternion.GetRotationAround(v1, q1); - Assert.AreEqual(q, new Quaternion(0, 1, 0, 0), "GetRotationAround 0 1 0 , 0 0.7 -0.7 0"); - - v1 = new(0, 0, 0); - q = Quaternion.GetRotationAround(v1, q1); - bool r = float.IsNaN(q.x) && float.IsNaN(q.y) && float.IsNaN(q.z) && float.IsNaN(q.w); - Assert.IsTrue(r, "GetRotationAround 0 0 0 , 0 0.7 -0.7 0"); - } - - [Test, Ignore("ToDo")] - public void GetSwingTwist() { } - - [Test, Ignore("ToDo")] - public void Dot() { } - - } -} -#endif \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/test/SphericalTest.cs b/NanoBrain/LinearAlgebra/test/SphericalTest.cs deleted file mode 100644 index 658ca04..0000000 --- a/NanoBrain/LinearAlgebra/test/SphericalTest.cs +++ /dev/null @@ -1,271 +0,0 @@ -#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 { 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 { 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 { 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 { 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 { - 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 { - 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 dirs = new List { - 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 \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/test/SwingTwistTest.cs b/NanoBrain/LinearAlgebra/test/SwingTwistTest.cs deleted file mode 100644 index 5f05a96..0000000 --- a/NanoBrain/LinearAlgebra/test/SwingTwistTest.cs +++ /dev/null @@ -1,131 +0,0 @@ -#if !UNITY_5_6_OR_NEWER -using NUnit.Framework; - -namespace LinearAlgebra.Test { - - [TestFixture] - public class SwingTwistTest { - - [Test] - public void Degrees_CreatesSwingTwistWithDegreeAngles() { - SwingTwist st = SwingTwist.Degrees(45, 30, 15); - Assert.IsNotNull(st); - Assert.AreEqual(45, st.swing.horizontal.inDegrees, 0.01f); - Assert.AreEqual(30, st.swing.vertical.inDegrees, 0.01f); - Assert.AreEqual(15, st.twist.inDegrees, 0.01f); - } - - [Test] - public void Radians_CreatesSwingTwistWithRadianAngles() { - float pi = (float)System.Math.PI; - SwingTwist st = SwingTwist.Radians(pi / 4, pi / 6, pi / 12); - Assert.IsNotNull(st); - Assert.AreEqual(45, st.swing.horizontal.inDegrees, 0.01f); - Assert.AreEqual(30, st.swing.vertical.inDegrees, 0.01f); - Assert.AreEqual(15, st.twist.inDegrees, 0.01f); - } - - [Test] - public void Zero_CreatesZeroRotation() { - SwingTwist st = SwingTwist.zero; - Assert.AreEqual(0, st.swing.horizontal.inDegrees, 0.01f); - Assert.AreEqual(0, st.swing.vertical.inDegrees, 0.01f); - Assert.AreEqual(0, st.twist.inDegrees, 0.01f); - } - - [Test] - public void QuaternionTest() { - Quaternion q; - SwingTwist s; - Quaternion r; - - q = Quaternion.identity; - s = SwingTwist.FromQuaternion(q); - r = s.ToQuaternion(); - Assert.AreEqual(q, r); - - q = Quaternion.Euler(90, 0, 0); - s = SwingTwist.FromQuaternion(q); - Assert.AreEqual(0, s.swing.horizontal.inDegrees, 10e-2f); - Assert.AreEqual(90, s.swing.vertical.inDegrees, 10e-2f); - Assert.AreEqual(0, s.twist.inDegrees, 0.01f); - r = s.ToQuaternion(); - Assert.AreEqual(0, Quaternion.UnsignedAngle(q, r), 10e-2f); - - q = Quaternion.Euler(0, 90, 0); - s = SwingTwist.FromQuaternion(q); - Assert.AreEqual(90, s.swing.horizontal.inDegrees,10e-2f); - Assert.AreEqual(0, s.swing.vertical.inDegrees, 0.01f); - Assert.AreEqual(0, s.twist.inDegrees, 0.01f); - r = s.ToQuaternion(); - Assert.AreEqual(0, Quaternion.UnsignedAngle(q, r), 10e-2f); - - q = Quaternion.Euler(0, 0, 90); - s = SwingTwist.FromQuaternion(q); - Assert.AreEqual(0, s.swing.horizontal.inDegrees, 0.01f); - Assert.AreEqual(0, s.swing.vertical.inDegrees, 0.01f); - Assert.AreEqual(90, s.twist.inDegrees, 0.01f); - r = s.ToQuaternion(); - Assert.AreEqual(0, Quaternion.UnsignedAngle(q, r), 10e-2f); - - q = Quaternion.Euler(0, 180, 0); - s = SwingTwist.FromQuaternion(q); - Assert.AreEqual(-180, s.swing.horizontal.inDegrees, 0.01f); - Assert.AreEqual(0, s.swing.vertical.inDegrees, 0.01f); - Assert.AreEqual(0, s.twist.inDegrees, 0.01f); - r = s.ToQuaternion(); - Assert.AreEqual(0, Quaternion.UnsignedAngle(q, r), 10e-2f); - - q = Quaternion.Euler(0, 135, 0); - s = SwingTwist.FromQuaternion(q); - Assert.AreEqual(135, s.swing.horizontal.inDegrees, 0.01f); - Assert.AreEqual(0, s.swing.vertical.inDegrees, 0.01f); - Assert.AreEqual(0, s.twist.inDegrees, 0.01f); - r = s.ToQuaternion(); - Assert.AreEqual(0, Quaternion.UnsignedAngle(q, r), 10e-2f); - - q = Quaternion.Euler(60, 45, 30); - s = SwingTwist.FromQuaternion(q); - Assert.AreEqual(45, s.swing.horizontal.inDegrees, 0.01f); - Assert.AreEqual(60, s.swing.vertical.inDegrees, 0.01f); - Assert.AreEqual(30, s.twist.inDegrees, 0.01f); - // r = s.ToQuaternion(); - // Assert.AreEqual(0, Quaternion.UnsignedAngle(q, r), 10e-2f); - - // q = Quaternion.Euler(-45, -30, -15); - // s = SwingTwist.FromQuaternion(q); - // Assert.AreEqual(-30, s.swing.horizontal.inDegrees, 0.01f); - // Assert.AreEqual(-45, s.swing.vertical.inDegrees, 0.01f); - // Assert.AreEqual(-15, s.twist.inDegrees, 0.01f); - // r = s.ToQuaternion(); - // Assert.AreEqual(0, Quaternion.UnsignedAngle(q, r), 10e-2f); - - // q = Quaternion.Euler(180, 180, 180); - // s = SwingTwist.FromQuaternion(q); - // Assert.AreEqual(-180, s.swing.horizontal.inDegrees, 0.01f); - // Assert.AreEqual(-180, s.swing.vertical.inDegrees, 0.01f); - // Assert.AreEqual(-180, s.twist.inDegrees, 0.01f); - // r = s.ToQuaternion(); - // Assert.AreEqual(0, Quaternion.UnsignedAngle(q, r), 10e-2f); - - } - - [Test] - public void ToAngleAxis_ConvertsToSpherical() { - SwingTwist st = SwingTwist.Degrees(45, 30, 15); - Spherical s = st.ToAngleAxis(); - Assert.IsNotNull(s); - } - - [Test] - public void FromAngleAxis_ConvertsFromSpherical() { - Spherical s = new(90, Direction.Degrees(45, 0)); - SwingTwist st = SwingTwist.FromAngleAxis(s); - Assert.IsNotNull(st); - } - - } - -} - -#endif \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/test/Vector2FloatTest.cs b/NanoBrain/LinearAlgebra/test/Vector2FloatTest.cs deleted file mode 100644 index 867765a..0000000 --- a/NanoBrain/LinearAlgebra/test/Vector2FloatTest.cs +++ /dev/null @@ -1,364 +0,0 @@ -#if !UNITY_5_6_OR_NEWER -using NUnit.Framework; - -namespace LinearAlgebra.Test { - using Vector2 = Vector2Float; - - public class Vector2FloatTest { - - [SetUp] - public void Setup() { - } - - [Test] - public void FromPolar() { - } - - [Test] - public void Equality() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - - Assert.IsFalse(v1 == v2, "4 5 == 1 2"); - Assert.IsTrue(v1 != v2, "4 5 != 1 2"); - - v2 = new(4, 5); - Assert.IsTrue(v1 == v2, "4 5 == 4 5"); - Assert.IsFalse(v1 != v2, "4 5 != 4 5"); - } - - [Test] - public void Magnitude() { - Vector2 v = new(1, 2); - float m = 0; - m = v.magnitude; - Assert.AreEqual(m, 2.236068F, "v.magnitude 1 2"); - - m = Vector2.MagnitudeOf(v); - Assert.AreEqual(m, 2.236068F, "MagnitudeOf 1 2"); - - v = new(-1, -2); - m = v.magnitude; - Assert.AreEqual(m, 2.236068F, "v.magnitude -1 -2"); - - v = new(0, 0); - m = v.magnitude; - Assert.AreEqual(m, 0, "v.magnitude 0 0"); - } - - [Test] - public void SqrMagnitude() { - Vector2 v = new(1, 2); - float m = 0; - - m = v.sqrMagnitude; - Assert.AreEqual(m, 5, "v.sqrMagnitude 1 2"); - - m = Vector2.SqrMagnitudeOf(v); - Assert.AreEqual(m, 5, "SqrMagnitudeOf 1 2"); - - v = new(-1, -2); - m = v.sqrMagnitude; - Assert.AreEqual(m, 5, "v.sqrMagnitude -1 -2"); - - v = new(0, 0); - m = v.sqrMagnitude; - Assert.AreEqual(m, 0, "v.sqrMagnitude 0 0"); - } - - [Test] - public void Distance() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - float f = 0; - - f = Vector2.Distance(v1, v2); - Assert.AreEqual(f, 4.24264002f, 1.0E-05F, "Distance(4 5, 1 2)"); - - v2 = new(-1, -2); - f = Vector2.Distance(v1, v2); - Assert.AreEqual(f, 8.602325F, "Distance(4 5, 1 2)"); - - v2 = new(0, 0); - f = Vector2.Distance(v1, v2); - Assert.AreEqual(f, 6.403124F, 1.0E-05F, "Distance(4 5, 1 2)"); - } - - [Test] - public void Normalize() { - Vector2 v = new(0, 3); - Vector2Float r; - - r = v.normalized; - Assert.AreEqual(0, r.horizontal, "normalized 0 3 H"); - Assert.AreEqual(1, r.vertical, "normalized 0 3 V"); - - r = Vector2.Normalize(v); - Assert.AreEqual(0, r.horizontal, "Normalize 0 3 H"); - Assert.AreEqual(1, r.vertical, "Normalize 0 3 V"); - - v = new(0, -3); - r = v.normalized; - Assert.AreEqual(0, r.horizontal, "normalized 0 -3 H"); - Assert.AreEqual(-1, r.vertical, "normalized 0 -3 V"); - - v = new(0, 0); - r = v.normalized; - Assert.AreEqual(0, r.horizontal, "normalized 0 0 H"); - Assert.AreEqual(0, r.vertical, "normalized 0 0 V"); - } - - [Test] - public void Negate() { - Vector2 v = new(4, 5); - Vector2 r; - - r = -v; - Assert.AreEqual(-4, r.horizontal, "- 4 5 H"); - Assert.AreEqual(-5, r.vertical, "- 4 5 V"); - - v = new(-4, -5); - r = -v; - Assert.AreEqual(4, r.horizontal, "- -4 -5 H"); - Assert.AreEqual(5, r.vertical, "- -4 -5 V"); - - v = new(0, 0); - r = -v; - Assert.AreEqual(0, r.horizontal, "- 0 0 H"); - Assert.AreEqual(0, r.vertical, "- 0 0 V"); - } - - [Test] - public void Subtract() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - Vector2 r = Vector2.zero; - - r = v1 - v2; - Assert.IsTrue(r == new Vector2(3, 3), "4 5 - 1 2"); - - v2 = new(-1, -2); - r = v1 - v2; - Assert.IsTrue(r == new Vector2(5, 7), "4 5 - -1 -2"); - - v2 = new(4, 5); - r = v1 - v2; - Assert.IsTrue(r == new Vector2(0, 0), "4 5 - 4 5"); - r = v1; - r -= v2; - Assert.AreEqual(r, new Vector2(0, 0), "4 5 - 4 5"); - - v2 = new(0, 0); - r = v1 - v2; - Assert.AreEqual(r, new Vector2(4, 5), "4 5 - 0 0"); - r -= v2; - Assert.AreEqual(r, new Vector2(4, 5), "4 5 - 0 0"); - } - - [Test] - public void Addition() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - Vector2 r = Vector2.zero; - - r = v1 + v2; - Assert.IsTrue(r == new Vector2(5, 7), "4 5 + 1 2"); - - v2 = new(-1, -2); - r = v1 + v2; - Assert.IsTrue(r == new Vector2(3, 3), "4 5 + -1 -2"); - r = v1; - r += v2; - Assert.AreEqual(r, new Vector2(3, 3), "4 5 + -1 -2"); - - v2 = new(0, 0); - r = v1 + v2; - Assert.AreEqual(r, new Vector2(4, 5), "4 5 + 0 0"); - r += v2; - Assert.AreEqual(r, new Vector2(4, 5), "4 5 + 0 0"); - } - - [Test] - public void Scale() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - Vector2 r; - - r = Vector2.Scale(v1, v2); - Assert.AreEqual(4, r.horizontal, "Scale 4 5 , 1 2 H"); - Assert.AreEqual(10, r.vertical, "Scale 4 5 , 1 2 V"); - - v2 = new(-1, -2); - r = Vector2.Scale(v1, v2); - Assert.AreEqual(-4, r.horizontal, "Scale 4 5 , -1 -2 H"); - Assert.AreEqual(-10, r.vertical, "Scale 4 5 , -1 -2 V"); - - v2 = new(0, 0); - r = Vector2.Scale(v1, v2); - Assert.AreEqual(0, r.horizontal, "Scale 4 5 , 0 0 H"); - Assert.AreEqual(0, r.vertical, "Scale 4 5 , 0 0 V"); - } - - [Test] - public void Multiply() { - Vector2 v1 = new(4, 5); - int f = 3; - Vector2 r; - - r = v1 * f; - Assert.AreEqual(12, r.horizontal, "4 5 * 3 H"); - Assert.AreEqual(15, r.vertical, "4 5 * 3 V"); - - r = f * v1; - Assert.AreEqual(12, r.horizontal, "3 * 4 5 H"); - Assert.AreEqual(15, r.vertical, "3 * 4 5 V"); - - f = -3; - r = v1 * f; - Assert.AreEqual(-12, r.horizontal, "4 5 * -3 H"); - Assert.AreEqual(-15, r.vertical, "4 5 * -3 V"); - - f = 0; - r = v1 * f; - Assert.AreEqual(0, r.horizontal, "4 5 * 0 H"); - Assert.AreEqual(0, r.vertical, "4 5 * 0 V"); - } - - [Test] - public void Divide() { - Vector2 v1 = new(4, 5); - float f = 2; - Vector2 r; - - r = v1 / f; - Assert.AreEqual(2, r.horizontal, "4 5 / 2 H"); - Assert.AreEqual(2.5, r.vertical, "4 5 / 2 V"); - - f = -2; - r = v1 / f; - Assert.AreEqual(-2, r.horizontal, "4 5 / -2 H"); - Assert.AreEqual(-2.5, r.vertical, "4 5 / -2 V"); - - f = 0; - r = v1 / f; - Assert.AreEqual(float.PositiveInfinity, r.horizontal, "4 5 / 0 H"); - Assert.AreEqual(float.PositiveInfinity, r.vertical, "4 5 / 0 V"); - } - - [Test] - public void Dot() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - float f; - - f = Vector2.Dot(v1, v2); - Assert.AreEqual(14, f, "Dot(4 5, 1 2)"); - - v2 = new(-1, -2); - f = Vector2.Dot(v1, v2); - Assert.AreEqual(-14, f, "Dot(4 5, -1 -2)"); - - v2 = new(0, 0); - f = Vector2.Dot(v1, v2); - Assert.AreEqual(0, f, "Dot(4 5, 0 0)"); - } - - [Test] - public void SignedAngle() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - float f; - - f = Vector2.SignedAngle(v1, v2); - Assert.AreEqual(-12.094758f, f); - - v2 = new(-1, -2); - f = Vector2.SignedAngle(v1, v2); - Assert.AreEqual(167.905228f, f); - - v2 = new(0, 0); - f = Vector2.SignedAngle(v1, v2); - Assert.AreEqual(0, f); - - v1 = new(0, 1); - v2 = new(1, 0); - f = Vector2.SignedAngle(v1, v2); - Assert.AreEqual(90, f); - - v1 = new(0, 1); - v2 = new(0, -1); - f = Vector2.SignedAngle(v1, v2); - Assert.AreEqual(180, f); - } - - [Test] - public void UnsignedAngle() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - float f; - - f = Vector2.UnsignedAngle(v1, v2); - Assert.AreEqual(12.094758f, f); - - v2 = new(-1, -2); - f = Vector2.UnsignedAngle(v1, v2); - Assert.AreEqual(167.905228f, f); - - v2 = new(0, 0); - f = Vector2.UnsignedAngle(v1, v2); - Assert.AreEqual(0, f); - - v1 = new(0, 1); - v2 = new(1, 0); - f = Vector2.UnsignedAngle(v1, v2); - Assert.AreEqual(90, f); - - v1 = new(0, 1); - v2 = new(0, -1); - f = Vector2.UnsignedAngle(v1, v2); - Assert.AreEqual(180, f); - } - - [Test] - public void Rotate() { - Vector2 v1 = new(1, 2); - Vector2 r; - - r = Vector2.Rotate(v1, AngleFloat.Degrees(0)); - Assert.AreEqual(0, Vector2.Distance(r, v1)); - - r = Vector2.Rotate(v1, AngleFloat.Degrees(180)); - Assert.AreEqual(0, Vector2.Distance(r, new Vector2(-1, -2)), 1.0e-06); - - r = Vector2.Rotate(v1, AngleFloat.Degrees(-90)); - Assert.AreEqual(0, Vector2.Distance(r, new Vector2(2, -1)), 1.0e-06); - - r = Vector2.Rotate(v1, AngleFloat.Degrees(270)); - Assert.AreEqual(0, Vector2.Distance(r, new Vector2(2, -1)), 1.0e-06); - } - - [Test] - public void Lerp() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - Vector2 r; - - r = Vector2.Lerp(v1, v2, 0); - Assert.AreEqual(0, Vector2.Distance(r, v1), 0); - - r = Vector2.Lerp(v1, v2, 1); - Assert.AreEqual(0, Vector2.Distance(r, v2), 0); - - r = Vector2.Lerp(v1, v2, 0.5f); - Assert.AreEqual(0, Vector2.Distance(r, new Vector2(2.5f, 3.5f)), 0); - - r = Vector2.Lerp(v1, v2, -1); - Assert.AreEqual(0, Vector2.Distance(r, new Vector2(7, 8)), 0); - - r = Vector2.Lerp(v1, v2, 2); - Assert.AreEqual(0, Vector2.Distance(r, new Vector2(-2, -1)), 0); - } - - } -} -#endif \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/test/Vector2IntTest.cs b/NanoBrain/LinearAlgebra/test/Vector2IntTest.cs deleted file mode 100644 index 3647ca0..0000000 --- a/NanoBrain/LinearAlgebra/test/Vector2IntTest.cs +++ /dev/null @@ -1,270 +0,0 @@ -#if !UNITY_5_6_OR_NEWER -using NUnit.Framework; - -namespace LinearAlgebra.Test { - using Vector2 = Vector2Int; - - public class Vector2IntTest { - - [SetUp] - public void Setup() { - } - - [Test] - public void FromPolar() { - } - - [Test] - public void Equality() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - - Assert.IsFalse(v1 == v2, "4 5 == 1 2"); - Assert.IsTrue(v1 != v2, "4 5 != 1 2"); - - v2 = new(4, 5); - Assert.IsTrue(v1 == v2, "4 5 == 4 5"); - Assert.IsFalse(v1 != v2, "4 5 != 4 5"); - } - - [Test] - public void Magnitude() { - Vector2 v = new(1, 2); - float m = 0; - m = v.magnitude; - Assert.AreEqual(m, 2.236068F, "v.magnitude 1 2"); - - m = Vector2.MagnitudeOf(v); - Assert.AreEqual(m, 2.236068F, "MagnitudeOf 1 2"); - - v = new(-1, -2); - m = v.magnitude; - Assert.AreEqual(m, 2.236068F, "v.magnitude -1 -2"); - - v = new(0, 0); - m = v.magnitude; - Assert.AreEqual(m, 0, "v.magnitude 0 0"); - } - - [Test] - public void SqrMagnitude() { - Vector2 v = new(1, 2); - float m = 0; - - m = v.sqrMagnitude; - Assert.AreEqual(m, 5, "v.sqrMagnitude 1 2"); - - m = Vector2.SqrMagnitudeOf(v); - Assert.AreEqual(m, 5, "SqrMagnitudeOf 1 2"); - - v = new(-1, -2); - m = v.sqrMagnitude; - Assert.AreEqual(m, 5, "v.sqrMagnitude -1 -2"); - - v = new(0, 0); - m = v.sqrMagnitude; - Assert.AreEqual(m, 0, "v.sqrMagnitude 0 0"); - } - - [Test] - public void Distance() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - float f = 0; - - f = Vector2.Distance(v1, v2); - Assert.AreEqual(f, 4.24264002f, 1.0E-05F, "Distance(4 5, 1 2)"); - - v2 = new(-1, -2); - f = Vector2.Distance(v1, v2); - Assert.AreEqual(f, 8.602325F, "Distance(4 5, 1 2)"); - - v2 = new(0, 0); - f = Vector2.Distance(v1, v2); - Assert.AreEqual(f, 6.403124F, 1.0E-05F, "Distance(4 5, 1 2)"); - } - - [Test] - public void Normalize() { - Vector2 v = new(0, 3); - Vector2Float r; - - r = v.normalized; - Assert.AreEqual(0, r.horizontal, "normalized 0 3 H"); - Assert.AreEqual(1, r.vertical, "normalized 0 3 V"); - - r = Vector2.Normalize(v); - Assert.AreEqual(0, r.horizontal, "Normalize 0 3 H"); - Assert.AreEqual(1, r.vertical, "Normalize 0 3 V"); - - v = new(0, -3); - r = v.normalized; - Assert.AreEqual(0, r.horizontal, "normalized 0 -3 H"); - Assert.AreEqual(-1, r.vertical, "normalized 0 -3 V"); - - v = new(0, 0); - r = v.normalized; - Assert.AreEqual(0, r.horizontal, "normalized 0 0 H"); - Assert.AreEqual(0, r.vertical, "normalized 0 0 V"); - } - - [Test] - public void Negate() { - Vector2 v = new(4, 5); - Vector2 r; - - r = -v; - Assert.AreEqual(-4, r.horizontal, "- 4 5 H"); - Assert.AreEqual(-5, r.vertical, "- 4 5 V"); - - v = new(-4, -5); - r = -v; - Assert.AreEqual(4, r.horizontal, "- -4 -5 H"); - Assert.AreEqual(5, r.vertical, "- -4 -5 V"); - - v = new(0, 0); - r = -v; - Assert.AreEqual(0, r.horizontal, "- 0 0 H"); - Assert.AreEqual(0, r.vertical, "- 0 0 V"); - } - - [Test] - public void Subtract() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - Vector2 r = Vector2.zero; - - r = v1 - v2; - Assert.IsTrue(r == new Vector2(3, 3), "4 5 - 1 2"); - - v2 = new(-1, -2); - r = v1 - v2; - Assert.IsTrue(r == new Vector2(5, 7), "4 5 - -1 -2"); - - v2 = new(4, 5); - r = v1 - v2; - Assert.IsTrue(r == new Vector2(0, 0), "4 5 - 4 5"); - r = v1; - r -= v2; - Assert.AreEqual(r, new Vector2(0, 0), "4 5 - 4 5"); - - v2 = new(0, 0); - r = v1 - v2; - Assert.AreEqual(r, new Vector2(4, 5), "4 5 - 0 0"); - r -= v2; - Assert.AreEqual(r, new Vector2(4, 5), "4 5 - 0 0"); - } - - [Test] - public void Addition() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - Vector2 r = Vector2.zero; - - r = v1 + v2; - Assert.IsTrue(r == new Vector2(5, 7), "4 5 + 1 2"); - - v2 = new(-1, -2); - r = v1 + v2; - Assert.IsTrue(r == new Vector2(3, 3), "4 5 + -1 -2"); - r = v1; - r += v2; - Assert.AreEqual(r, new Vector2(3, 3), "4 5 + -1 -2"); - - v2 = new(0, 0); - r = v1 + v2; - Assert.AreEqual(r, new Vector2(4, 5), "4 5 + 0 0"); - r += v2; - Assert.AreEqual(r, new Vector2(4, 5), "4 5 + 0 0"); - } - - [Test] - public void Scale() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - Vector2 r; - - r = Vector2.Scale(v1, v2); - Assert.AreEqual(4, r.horizontal, "Scale 4 5 , 1 2 H"); - Assert.AreEqual(10, r.vertical, "Scale 4 5 , 1 2 V"); - - v2 = new(-1, -2); - r = Vector2.Scale(v1, v2); - Assert.AreEqual(-4, r.horizontal, "Scale 4 5 , -1 -2 H"); - Assert.AreEqual(-10, r.vertical, "Scale 4 5 , -1 -2 V"); - - v2 = new(0, 0); - r = Vector2.Scale(v1, v2); - Assert.AreEqual(0, r.horizontal, "Scale 4 5 , 0 0 H"); - Assert.AreEqual(0, r.vertical, "Scale 4 5 , 0 0 V"); - } - - [Test] - public void Multiply() { - Vector2 v1 = new(4, 5); - int f = 3; - Vector2 r; - - r = v1 * f; - Assert.AreEqual(12, r.horizontal, "4 5 * 3 H"); - Assert.AreEqual(15, r.vertical, "4 5 * 3 V"); - - r = f * v1; - Assert.AreEqual(12, r.horizontal, "3 * 4 5 H"); - Assert.AreEqual(15, r.vertical, "3 * 4 5 V"); - - f = -3; - r = v1 * f; - Assert.AreEqual(-12, r.horizontal, "4 5 * -3 H"); - Assert.AreEqual(-15, r.vertical, "4 5 * -3 V"); - - f = 0; - r = v1 * f; - Assert.AreEqual(0, r.horizontal, "4 5 * 0 H"); - Assert.AreEqual(0, r.vertical, "4 5 * 0 V"); - } - - [Test] - public void Divide() { - Vector2 v1 = new(4, 5); - int f = 2; - Vector2 r; - - r = v1 / f; - Assert.AreEqual(2, r.horizontal, "4 5 / 2 H"); - Assert.AreEqual(2, r.vertical, "4 5 / 2 V"); - - f = -2; - r = v1 / f; - Assert.AreEqual(-2, r.horizontal, "4 5 / -2 H"); - Assert.AreEqual(-2, r.vertical, "4 5 / -2 V"); - - Assert.Throws(() => { - f = 0; - r = v1 / f; - Assert.AreEqual(float.PositiveInfinity, r.horizontal, "4 5 / 0 H"); - Assert.AreEqual(float.PositiveInfinity, r.vertical, "4 5 / 0 V"); - }); - } - - [Test] - public void Dot() { - Vector2 v1 = new(4, 5); - Vector2 v2 = new(1, 2); - int f; - - f = Vector2.Dot(v1, v2); - Assert.AreEqual(14, f, "Dot(4 5, 1 2)"); - - v2 = new(-1, -2); - f = Vector2.Dot(v1, v2); - Assert.AreEqual(-14, f, "Dot(4 5, -1 -2)"); - - v2 = new(0, 0); - f = Vector2.Dot(v1, v2); - Assert.AreEqual(0, f, "Dot(4 5, 0 0)"); - } - - } -} -#endif \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/test/Vector3FloatTest.cs b/NanoBrain/LinearAlgebra/test/Vector3FloatTest.cs deleted file mode 100644 index fd3c2dc..0000000 --- a/NanoBrain/LinearAlgebra/test/Vector3FloatTest.cs +++ /dev/null @@ -1,581 +0,0 @@ -#if !UNITY_5_6_OR_NEWER -using NUnit.Framework; - -namespace LinearAlgebra.Test { - using Vector3 = Vector3Float; - - public class Vector3FloatTest { - - [Test] - public void FromSpherical() { - Vector3 v = new(0, 0, 1); - Spherical s = Spherical.FromVector3(v); - Vector3 r = Vector3.FromSpherical(s); - - Assert.AreEqual(0, r.horizontal, "0 0 1"); - Assert.AreEqual(0, r.vertical, 1.0e-06, "0 0 1"); - Assert.AreEqual(1, r.depth, "0 0 1"); - - v = new(0, 1, 0); - s = Spherical.FromVector3(v); - r = Vector3.FromSpherical(s); - Assert.AreEqual(0, r.horizontal, "0 0 1"); - Assert.AreEqual(1, r.vertical, "0 0 1"); - Assert.AreEqual(0, r.depth, 1.0e-06, "0 0 1"); - - v = new(1, 0, 0); - s = Spherical.FromVector3(v); - r = Vector3.FromSpherical(s); - Assert.AreEqual(1, r.horizontal, "0 0 1"); - Assert.AreEqual(0, r.vertical, 1.0e-06, "0 0 1"); - Assert.AreEqual(0, r.depth, 1.0e-06, "0 0 1"); - } - - [Test] - public void Magnitude() { - Vector3 v = new(1, 2, 3); - float m = 0; - - m = v.magnitude; - Assert.AreEqual(3.7416575f, m, "magnitude 1 2 3"); - - m = Vector3.MagnitudeOf(v); - Assert.AreEqual(3.7416575f, m, "MagnitudeOf 1 2 3"); - - v = new(-1, -2, -3); - m = v.magnitude; - Assert.AreEqual(3.7416575f, m, "magnitude -1 -2 -3"); - - v = new(0, 0, 0); - m = v.magnitude; - Assert.AreEqual(0, m, "magnitude 0 0 0"); - - // Infinity tests are still missing - } - - [Test] - public void SqrMagnitude() { - Vector3 v = new(1, 2, 3); - float m = 0; - - m = v.sqrMagnitude; - Assert.AreEqual(14, m, "sqrMagnitude 1 2 3"); - - m = Vector3.SqrMagnitudeOf(v); - Assert.AreEqual(14, m, "SqrMagnitudeOf 1 2 3"); - - v = new(-1, -2, -3); - m = v.sqrMagnitude; - Assert.AreEqual(14, m, "sqrMagnitude -1 -2 -3"); - - v = new(0, 0, 0); - m = v.sqrMagnitude; - Assert.AreEqual(0, m, "sqrMagnitude 0 0 0"); - - // Infinity tests are still missing - } - - [Test] - public void Normalize() { - Vector3 v = new(0, 2, 0); - Vector3 r; - - r = v.normalized; - Assert.AreEqual(new Vector3(0, 1, 0), r, "normalized 0 2 0"); - - r = Vector3.Normalize(v); - Assert.AreEqual(new Vector3(0, 1, 0), r, "Normalize 0 2 0"); - - v = new(0, -2, 0); - r = v.normalized; - Assert.AreEqual(new Vector3(0, -1, 0), r, "normalized 0 -2 0"); - v = new(0, 0, 0); - r = v.normalized; - Assert.AreEqual(new Vector3(0, 0, 0), r, "normalized 0 0 0"); - - v = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - r = v.normalized; - Assert.IsTrue(float.IsNaN(r.horizontal), "normalized infinity infinity infinity"); - Assert.IsTrue(float.IsNaN(r.vertical), "normalized infinity infinity infinity"); - Assert.IsTrue(float.IsNaN(r.depth), "normalized infinity infinity infinity"); - - v = new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); - r = v.normalized; - Assert.IsTrue(float.IsNaN(r.horizontal), "normalized -infinity -infinity -infinity"); - Assert.IsTrue(float.IsNaN(r.vertical), "normalized -infinity -infinity -infinity"); - Assert.IsTrue(float.IsNaN(r.depth), "normalized -infinity -infinity -infinity"); - } - - [Test] - public void Negate() { - Vector3 v = new(4, 5, 6); - Vector3 r; - - r = -v; - Assert.AreEqual(-4, r.horizontal, "- 4 5 6 H"); - Assert.AreEqual(-5, r.vertical, "- 4 5 6 V"); - Assert.AreEqual(-6, r.depth, "- 4 5 6 D"); - - v = new(-4, -5, -6); - r = -v; - Assert.AreEqual(4, r.horizontal, "- -4 -5 -6 H"); - Assert.AreEqual(5, r.vertical, "- -4 -5 -6 V"); - Assert.AreEqual(6, r.depth, "- -4 -5 -6 D"); - - v = new(0, 0, 0); - r = -v; - Assert.AreEqual(new Vector3(0, 0, 0), r, "- 0 0 0"); - Assert.AreEqual(0, r.horizontal, "- 0 0 0 H"); - Assert.AreEqual(0, r.vertical, "- 0 0 0 V"); - Assert.AreEqual(0, r.depth, "- 0 0 0 D"); - - - v = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - r = -v; - Assert.AreEqual(new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity), r, "- inifinty infinity infinity"); - - v = new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); - r = -v; - Assert.AreEqual(new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity), r, "- -inifinty -infinity -infinity"); - } - - [Test] - public void Subtract() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 r = Vector3.zero; - - r = v1 - v2; - Assert.IsTrue(r == new Vector3(3, 3, 3), "4 5 6 - 1 2 3"); - - v2 = new(-1, -2, -3); - r = v1 - v2; - Assert.IsTrue(r == new Vector3(5, 7, 9), "4 5 6 - -1 -2 -3"); - - v2 = new(4, 5, 6); - r = v1 - v2; - Assert.IsTrue(r == new Vector3(0, 0, 0), "4 5 6 - 4 5 6"); - r = v1; - r -= v2; - Assert.AreEqual(r, new Vector3(0, 0, 0), "4 5 6 - 4 5 6"); - - v2 = new(0, 0, 0); - r = v1 - v2; - Assert.AreEqual(r, new Vector3(4, 5, 6), "4 5 6 - 0 0 0"); - r -= v2; - Assert.AreEqual(r, new Vector3(4, 5, 6), "4 5 6 - 0 0 0"); - - // Infinity tests are still missing - } - - [Test] - public void Addition() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 r = Vector3.zero; - - r = v1 + v2; - Assert.IsTrue(r == new Vector3(5, 7, 9), "4 5 6 + 1 2 3"); - - v2 = new(-1, -2, -3); - r = v1 + v2; - Assert.IsTrue(r == new Vector3(3, 3, 3), "4 5 6 + -1 -2 -3"); - r = v1; - r += v2; - Assert.AreEqual(r, new Vector3(3, 3, 3), "4 5 6 + -1 -2 -3"); - - v2 = new(0, 0, 0); - r = v1 + v2; - Assert.AreEqual(r, new Vector3(4, 5, 6), "4 5 6 + 0 0 0"); - r += v2; - Assert.AreEqual(r, new Vector3(4, 5, 6), "4 5 6 + 0 0 0"); - - // Infinity tests are still missing - } - - [Test] - public void Scale() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 r; - - r = Vector3.Scale(v1, v2); - Assert.AreEqual(4, r.horizontal, "Scale 4 5 6 , 1 2 3 H"); - Assert.AreEqual(10, r.vertical, "Scale 4 5 6 , 1 2 3 V"); - Assert.AreEqual(18, r.depth, "Scale 4 5 6 , 1 2 3 D"); - - v2 = new(-1, -2, -3); - r = Vector3.Scale(v1, v2); - Assert.AreEqual(-4, r.horizontal, "Scale 4 5 6 , -1 -2 -3 H"); - Assert.AreEqual(-10, r.vertical, "Scale 4 5 6 , -1 -2 -3 V"); - Assert.AreEqual(-18, r.depth, "Scale 4 5 6 , -1 -2 -3 D"); - - v2 = new(0, 0, 0); - r = Vector3.Scale(v1, v2); - Assert.AreEqual(0, r.horizontal, "Scale 4 5 6 , 0 0 0 H"); - Assert.AreEqual(0, r.vertical, "Scale 4 5 6 , 0 0 0 V"); - Assert.AreEqual(0, r.depth, "Scale 4 5 6 , 0 0 0 D"); - - v2 = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - r = Vector3.Scale(v1, v2); - Assert.AreEqual(float.PositiveInfinity, r.horizontal, "Scale 4 5 6 , inf inf inf H"); - Assert.AreEqual(float.PositiveInfinity, r.vertical, "Scale 4 5 6 , inf inf inf V"); - Assert.AreEqual(float.PositiveInfinity, r.depth, "Scale 4 5 6 , inf inf inf D"); - - v2 = new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); - r = Vector3.Scale(v1, v2); - Assert.AreEqual(float.NegativeInfinity, r.horizontal, "Scale 4 5 6 , -inf -inf -inf H"); - Assert.AreEqual(float.NegativeInfinity, r.vertical, "Scale 4 5 6 , -inf -inf -inf V"); - Assert.AreEqual(float.NegativeInfinity, r.depth, "Scale 4 5 6 , -inf -inf -inf D"); - } - - [Test] - public void Multiply() { - Vector3 v1 = new(4, 5, 6); - float f = 3; - Vector3 r; - - r = v1 * f; - Assert.AreEqual(12, r.horizontal, "4 5 6 * 3 H"); - Assert.AreEqual(15, r.vertical, "4 5 6 * 3 V"); - Assert.AreEqual(18, r.depth, "4 5 6 * 3 D"); - - f = -3; - r = v1 * f; - Assert.AreEqual(-12, r.horizontal, "4 5 6 * -3 H"); - Assert.AreEqual(-15, r.vertical, "4 5 6 * -3 V"); - Assert.AreEqual(-18, r.depth, "4 5 6 * -3 D"); - - f = 0; - r = v1 * f; - Assert.AreEqual(0, r.horizontal, "4 5 6 * 0 H"); - Assert.AreEqual(0, r.vertical, "4 5 6 * 0 V"); - Assert.AreEqual(0, r.depth, "4 5 6 * 0 D"); - - f = float.PositiveInfinity; - r = v1 * f; - Assert.AreEqual(float.PositiveInfinity, r.horizontal, "4 5 6 * inf H"); - Assert.AreEqual(float.PositiveInfinity, r.vertical, "4 5 6 * inf V"); - Assert.AreEqual(float.PositiveInfinity, r.depth, "4 5 6 * inf D"); - - f = float.NegativeInfinity; - r = v1 * f; - Assert.AreEqual(float.NegativeInfinity, r.horizontal, "4 5 6 * -inf H"); - Assert.AreEqual(float.NegativeInfinity, r.vertical, "4 5 6 * -inf V"); - Assert.AreEqual(float.NegativeInfinity, r.depth, "4 5 6 * -inf D"); - } - - [Test] - public void Divide() { - Vector3 v1 = new(4, 5, 6); - float f = 2; - Vector3 r; - - r = v1 / f; - Assert.AreEqual(2, r.horizontal, "4 5 6 / 2 H"); - Assert.AreEqual(2.5, r.vertical, "4 5 6 / 2 V"); - Assert.AreEqual(3, r.depth, "4 5 6 / 2 D"); - - f = -2; - r = v1 / f; - Assert.AreEqual(-2, r.horizontal, "4 5 6 / -2 H"); - Assert.AreEqual(-2.5, r.vertical, "4 5 6 / -2 V"); - Assert.AreEqual(-3, r.depth, "4 5 6 / -2 D"); - - f = 0; - r = v1 / f; - Assert.AreEqual(float.PositiveInfinity, r.horizontal, "4 5 6 / 0 H"); - Assert.AreEqual(float.PositiveInfinity, r.vertical, "4 5 6 / 0 V"); - Assert.AreEqual(float.PositiveInfinity, r.depth, "4 5 6 / 0 D"); - - f = float.PositiveInfinity; - r = v1 / f; - Assert.AreEqual(0, r.horizontal, "4 5 6 / inf H"); - Assert.AreEqual(0, r.vertical, "4 5 6 / inf V"); - Assert.AreEqual(0, r.depth, "4 5 6 / inf D"); - - f = float.NegativeInfinity; - r = v1 / f; - Assert.AreEqual(0, r.horizontal, "4 5 6 / -inf H"); - Assert.AreEqual(0, r.vertical, "4 5 6 / -inf V"); - Assert.AreEqual(0, r.depth, "4 5 6 / -inf D"); - } - - [Test] - public void Dot() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - float f; - - f = Vector3.Dot(v1, v2); - Assert.AreEqual(32, f, "Dot(4 5 6, 1 2 3)"); - - v2 = new(-1, -2, -3); - f = Vector3.Dot(v1, v2); - Assert.AreEqual(-32, f, "Dot(4 5 6, -1 -2 -3)"); - - v2 = new(0, 0, 0); - f = Vector3.Dot(v1, v2); - Assert.AreEqual(0, f, "Dot(4 5 6, 0 0 0)"); - - v2 = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - f = Vector3.Dot(v1, v2); - Assert.AreEqual(float.PositiveInfinity, f, "Dot(4 5 6, inf inf inf)"); - - v2 = new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); - f = Vector3.Dot(v1, v2); - Assert.AreEqual(float.NegativeInfinity, f, "Dot(4 5 6, -inf -inf -inf)"); - } - - [Test] - public void Equality() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - bool r; - - r = v1 == v2; - Assert.IsFalse(r, "4 5 6 == 1 2 3"); - - v2 = new(4, 5, 6); - r = v1 == v2; - Assert.IsTrue(r, "4 5 6 == 4 5 6"); - - v2 = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - r = v1 == v2; - Assert.IsFalse(r, "4 5 6 == inf inf inf"); - - v1 = new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); - r = v1 == v2; - Assert.IsFalse(r, "-inf -inf -inf == inf inf inf"); - } - - [Test] - public void Distance() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - float f; - - f = Vector3.Distance(v1, v2); - Assert.AreEqual(5.19615221F, f, "Distance(4 5 6, 1 2 3)"); - - v2 = new(-1, -2, -3); - f = Vector3.Distance(v1, v2); - Assert.AreEqual(12.4498997F, f, "Distance(4 5 6, -1 -2 -3)"); - - v2 = new(0, 0, 0); - f = Vector3.Distance(v1, v2); - Assert.AreEqual(v1.magnitude, f, "Distance(4 5 6, 0 0 0)"); - - v2 = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - f = Vector3.Distance(v1, v2); - Assert.AreEqual(float.PositiveInfinity, f, "Distance(4 5 6, inf inf inf)"); - - v2 = new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); - f = Vector3.Distance(v1, v2); - Assert.AreEqual(float.PositiveInfinity, f, "Distance(4 5 6, -inf -inf -inf)"); - } - - [Test] - public void Cross() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 r; - - r = Vector3.Cross(v1, v2); - Assert.AreEqual(3, r.horizontal, "Cross(4 5 6, 1 2 3) H"); - Assert.AreEqual(-6, r.vertical, "Cross(4 5 6, 1 2 3) V"); - Assert.AreEqual(3, r.depth, "Cross(4 5 6, 1 2 3) D"); - - v2 = new(-1, -2, -3); - r = Vector3.Cross(v1, v2); - Assert.AreEqual(-3, r.horizontal, "Cross(4 5 6, -1 -2 -3) H"); - Assert.AreEqual(6, r.vertical, "Cross(4 5 6, -1 -2 -3) V"); - Assert.AreEqual(-3, r.depth, "Cross(4 5 6, -1 -2 -3) D"); - - v2 = new(0, 0, 0); - r = Vector3.Cross(v1, v2); - Assert.AreEqual(0, r.horizontal, "Cross(4 5 6, 0 0 0) H"); - Assert.AreEqual(0, r.vertical, "Cross(4 5 6, 0 0 0) V"); - Assert.AreEqual(0, r.depth, "Cross(4 5 6, 0 0 0) D"); - - v2 = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - r = Vector3.Cross(v1, v2); - Assert.IsTrue(float.IsNaN(r.horizontal), "Cross(4 5 6, inf inf inf) H"); - Assert.IsTrue(float.IsNaN(r.vertical), "Cross(4 5 6, inf inf inf) V"); - Assert.IsTrue(float.IsNaN(r.depth), "Cross(4 5 6, inf inf inf) D"); - - v2 = new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); - r = Vector3.Cross(v1, v2); - Assert.IsTrue(float.IsNaN(r.horizontal), "Cross(4 5 6, -inf -inf -inf) H"); - Assert.IsTrue(float.IsNaN(r.vertical), "Cross(4 5 6, -inf -inf -inf) V"); - Assert.IsTrue(float.IsNaN(r.depth), "Cross(4 5 6, -inf -inf -inf) D"); - } - - [Test] - public void Project() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 r; - - r = Vector3.Project(v1, v2); - Assert.AreEqual(2.28571439F, r.horizontal, "Project(4 5 6, 1 2 3) H"); - Assert.AreEqual(4.57142878F, r.vertical, "Project(4 5 6, 1 2 3) V"); - Assert.AreEqual(6.85714293F, r.depth, "Project(4 5 6, 1 2 3) D"); - - v2 = new(-1, -2, -3); - r = Vector3.Project(v1, v2); - Assert.AreEqual(2.28571439F, r.horizontal, "Project(4 5 6, -1 -2 -3) H"); - Assert.AreEqual(4.57142878F, r.vertical, "Project(4 5 6, -1 -2 -3) V"); - Assert.AreEqual(6.85714293F, r.depth, "Project(4 5 6, -1 -2 -3) D"); - - v2 = new(0, 0, 0); - r = Vector3.Project(v1, v2); - Assert.AreEqual(0, r.horizontal, "Project(4 5 6, 0 0 0) H"); - Assert.AreEqual(0, r.vertical, "Project(4 5 6, 0 0 0) V"); - Assert.AreEqual(0, r.depth, "Project(4 5 6, 0 0 0) D"); - - r = Vector3.Project(v2, v1); - Assert.AreEqual(0, r.horizontal, "Project(0 0 0, 4 5 6) H"); - Assert.AreEqual(0, r.vertical, "Project(0 0 0, 4 5 6) V"); - Assert.AreEqual(0, r.depth, "Project(0 0 0, 4 5 6) D"); - - v2 = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - r = Vector3.Project(v1, v2); - Assert.IsTrue(float.IsNaN(r.horizontal), "Project(4 5 6, inf inf inf) H"); - Assert.IsTrue(float.IsNaN(r.vertical), "Project(4 5 6, inf inf inf) V"); - Assert.IsTrue(float.IsNaN(r.depth), "Project(4 5 6, inf inf inf) D"); - - v2 = new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); - r = Vector3.Project(v1, v2); - Assert.IsTrue(float.IsNaN(r.horizontal), "Project(4 5 6, -inf -inf -inf) H"); - Assert.IsTrue(float.IsNaN(r.vertical), "Project(4 5 6, -inf -inf -inf) V"); - Assert.IsTrue(float.IsNaN(r.depth), "Project(4 5 6, -inf -inf -inf) D"); - } - - [Test] - public void ProjectOnPlane() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 r; - - r = Vector3.ProjectOnPlane(v1, v2); - Assert.AreEqual(1.71428561F, r.horizontal, "ProjectOnPlane(4 5 6, 1 2 3) H"); - Assert.AreEqual(0.428571224F, r.vertical, "ProjectOnPlane(4 5 6, 1 2 3) V"); - Assert.AreEqual(-0.857142925F, r.depth, "ProjectOnPlane(4 5 6, 1 2 3) D"); - - v2 = new(-1, -2, -3); - r = Vector3.ProjectOnPlane(v1, v2); - Assert.AreEqual(1.71428561F, r.horizontal, "ProjectOnPlane(4 5 6, -1 -2 -3) H"); - Assert.AreEqual(0.428571224F, r.vertical, "ProjectOnPlane(4 5 6, -1 -2 -3) V"); - Assert.AreEqual(-0.857142925F, r.depth, "ProjectOnPlane(4 5 6, -1 -2 -3) D"); - - v2 = new(0, 0, 0); - r = Vector3.ProjectOnPlane(v1, v2); - Assert.AreEqual(4, r.horizontal, "ProjectOnPlane(4 5 6, 0 0 0) H"); - Assert.AreEqual(5, r.vertical, "ProjectOnPlane(4 5 6, 0 0 0) V"); - Assert.AreEqual(6, r.depth, "ProjectOnPlane(4 5 6, 0 0 0) D"); - - r = Vector3.ProjectOnPlane(v2, v1); - Assert.AreEqual(0, r.horizontal, "ProjectOnPlane(0 0 0, 4 5 6) H"); - Assert.AreEqual(0, r.vertical, "ProjectOnPlane(0 0 0, 4 5 6) V"); - Assert.AreEqual(0, r.depth, "ProjectOnPlane(0 0 0, 4 5 6) D"); - - v2 = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - r = Vector3.ProjectOnPlane(v1, v2); - Assert.IsTrue(float.IsNaN(r.horizontal), "ProjectOnPlane(4 5 6, inf inf inf) H"); - Assert.IsTrue(float.IsNaN(r.vertical), "ProjectOnPlane(4 5 6, inf inf inf) V"); - Assert.IsTrue(float.IsNaN(r.depth), "ProjectOnPlane(4 5 6, inf inf inf) D"); - - v2 = new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); - r = Vector3.ProjectOnPlane(v1, v2); - Assert.IsTrue(float.IsNaN(r.horizontal), "ProjectOnPlane(4 5 6, -inf -inf -inf) H"); - Assert.IsTrue(float.IsNaN(r.vertical), "ProjectOnPlane(4 5 6, -inf -inf -inf) V"); - Assert.IsTrue(float.IsNaN(r.depth), "ProjectOnPlane(4 5 6, -inf -inf -inf) D"); - } - - [Test] - public void UnsignedAngle() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - AngleFloat a; - - a = Vector3.UnsignedAngle(v1, v2); - Assert.AreEqual(12.9331379F, a.inDegrees, "Angle(4 5 6, 1 2 3)"); - - v2 = new(-1, -2, -3); - a = Vector3.UnsignedAngle(v1, v2); - Assert.AreEqual(167.066849F, a.inDegrees, "Angle(4 5 6, -1 -2 -3)"); - - v2 = new(0, 0, 0); - a = Vector3.UnsignedAngle(v1, v2); - Assert.AreEqual(0, a.inDegrees, "Angle(4 5 6, 0 0 0)"); - - v2 = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - a = Vector3.UnsignedAngle(v1, v2); - Assert.IsTrue(float.IsNaN(a.inDegrees), "Angle(4 5 6, inf inf inf)"); - - v2 = new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); - a = Vector3.UnsignedAngle(v1, v2); - Assert.IsTrue(float.IsNaN(a.inDegrees), "Angle(4 5 6, inf inf inf)"); - } - - [Test] - public void SignedAngle() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 v3 = new(7, 8, -9); - AngleFloat a; - - a = Vector3.SignedAngle(v1, v2, v3); - Assert.AreEqual(-12.9331379F, a.inDegrees, "SignedAngle(4 5 6, 1 2 3, 7 8 -9)"); - - v2 = new(-1, -2, -3); - a = Vector3.SignedAngle(v1, v2, v3); - Assert.AreEqual(167.066849F, a.inDegrees, "SignedAngle(4 5 6, -1 -2 -3, 7 8 -9)"); - - v2 = new(0, 0, 0); - a = Vector3.SignedAngle(v1, v2, v3); - Assert.AreEqual(0, a.inDegrees, "SignedAngle(4 5 6, 0 0 0, 7 8 -9)"); - - v2 = new(1, 2, 3); - v3 = new(-7, -8, 9); - a = Vector3.SignedAngle(v1, v2, v3); - Assert.AreEqual(12.9331379F, a.inDegrees, "SignedAngle(4 5 6, 1 2 3, -7 -8 9)"); - - v3 = new(0, 0, 0); - a = Vector3.SignedAngle(v1, v2, v3); - Assert.AreEqual(0, a.inDegrees, "SignedAngle(4 5 6, 1 2 3, 0 0 0)"); - - v2 = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - a = Vector3.SignedAngle(v1, v2, v3); - Assert.IsTrue(float.IsNaN(a.inDegrees), "SignedAngle(4 5 6, inf inf inf, 0 0 0)"); - - v2 = new(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); - a = Vector3.SignedAngle(v1, v2, v3); - Assert.IsTrue(float.IsNaN(a.inDegrees), "SignedAngle(4 5 6, -inf -inf -inf, 0 0 0)"); - } - - [Test] - public void Lerp() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 r; - - r = Vector3.Lerp(v1, v2, 0); - Assert.AreEqual(0, Vector3.Distance(r, v1), 0); - - r = Vector3.Lerp(v1, v2, 1); - Assert.AreEqual(0, Vector3.Distance(r, v2), 0); - - r = Vector3.Lerp(v1, v2, 0.5f); - Assert.AreEqual(0, Vector3.Distance(r, new Vector3(2.5f, 3.5f, 4.5f)), 0); - - r = Vector3.Lerp(v1, v2, -1); - Assert.AreEqual(0, Vector3.Distance(r, new Vector3(7, 8, 9)), 0); - - r = Vector3.Lerp(v1, v2, 2); - Assert.AreEqual(0, Vector3.Distance(r, new Vector3(-2, -1, 0)), 0); - } - } -} -#endif \ No newline at end of file diff --git a/NanoBrain/LinearAlgebra/test/Vector3IntTest.cs b/NanoBrain/LinearAlgebra/test/Vector3IntTest.cs deleted file mode 100644 index b718178..0000000 --- a/NanoBrain/LinearAlgebra/test/Vector3IntTest.cs +++ /dev/null @@ -1,349 +0,0 @@ -#if !UNITY_5_6_OR_NEWER -using NUnit.Framework; - -namespace LinearAlgebra.Test { - using Vector3 = Vector3Int; - - public class Vector3IntTest { - - [Test] - public void Equality() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - - Assert.IsFalse(v1 == v2, "4 5 6 == 1 2 3"); - Assert.IsTrue(v1 != v2, "4 5 6 != 1 2 3"); - - v2 = new(4, 5, 6); - Assert.IsTrue(v1 == v2, "4 5 6 == 4 5 6"); - Assert.IsFalse(v1 != v2, "4 5 6 != 4 5 6"); - } - - [Test] - public void Magnitude() { - Vector3 v = new(1, 2, 3); - float m = 0; - - m = v.magnitude; - Assert.AreEqual(3.7416575f, m, "magnitude 1 2 3"); - - m = Vector3.MagnitudeOf(v); - Assert.AreEqual(3.7416575f, m, "MagnitudeOf 1 2 3"); - - v = new(-1, -2, -3); - m = v.magnitude; - Assert.AreEqual(3.7416575f, m, "magnitude -1 -2 -3"); - - v = new(0, 0, 0); - m = v.magnitude; - Assert.AreEqual(0, m, "magnitude 0 0 0"); - - // Infinity tests are still missing - } - - [Test] - public void SqrMagnitude() { - Vector3 v = new(1, 2, 3); - float m = 0; - - m = v.sqrMagnitude; - Assert.AreEqual(14, m, "sqrMagnitude 1 2 3"); - - m = Vector3.SqrMagnitudeOf(v); - Assert.AreEqual(14, m, "SqrMagnitudeOf 1 2 3"); - - v = new(-1, -2, -3); - m = v.sqrMagnitude; - Assert.AreEqual(14, m, "sqrMagnitude -1 -2 -3"); - - v = new(0, 0, 0); - m = v.sqrMagnitude; - Assert.AreEqual(0, m, "sqrMagnitude 0 0 0"); - - // Infinity tests are still missing - } - - [Test] - public void Distance() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - float f; - - f = Vector3.Distance(v1, v2); - Assert.AreEqual(5.19615221F, f, "Distance(4 5 6, 1 2 3)"); - - v2 = new(-1, -2, -3); - f = Vector3.Distance(v1, v2); - Assert.AreEqual(12.4498997F, f, "Distance(4 5 6, -1 -2 -3)"); - - v2 = new(0, 0, 0); - f = Vector3.Distance(v1, v2); - Assert.AreEqual(v1.magnitude, f, "Distance(4 5 6, 0 0 0)"); - } - - [Test] - public void Normalize() { - Vector3 v = new(0, 2, 0); - Vector3Float r; - - r = v.normalized; - //Assert.AreEqual(new Vector3(0, 1, 0), r, "normalized 0 2 0"); - Assert.AreEqual(0, r.horizontal, "normalized 0 2 0"); - Assert.AreEqual(1, r.vertical, "normalized 0 2 0"); - Assert.AreEqual(0, r.depth, "normalized 0 2 0"); - - r = Vector3.Normalize(v); - Assert.AreEqual(new Vector3Float(0, 1, 0), r, "Normalize 0 2 0"); - - v = new(0, -2, 0); - r = v.normalized; - Assert.AreEqual(new Vector3Float(0, -1, 0), r, "normalized 0 -2 0"); - v = new(0, 0, 0); - r = v.normalized; - Assert.AreEqual(new Vector3Float(0, 0, 0), r, "normalized 0 0 0"); - } - - [Test] - public void Negate() { - Vector3 v = new(4, 5, 6); - Vector3 r; - - r = -v; - Assert.AreEqual(-4, r.horizontal, "- 4 5 6 H"); - Assert.AreEqual(-5, r.vertical, "- 4 5 6 V"); - Assert.AreEqual(-6, r.depth, "- 4 5 6 D"); - - v = new(-4, -5, -6); - r = -v; - Assert.AreEqual(4, r.horizontal, "- -4 -5 -6 H"); - Assert.AreEqual(5, r.vertical, "- -4 -5 -6 V"); - Assert.AreEqual(6, r.depth, "- -4 -5 -6 D"); - - v = new(0, 0, 0); - r = -v; - Assert.AreEqual(new Vector3(0, 0, 0), r, "- 0 0 0"); - Assert.AreEqual(0, r.horizontal, "- 0 0 0 H"); - Assert.AreEqual(0, r.vertical, "- 0 0 0 V"); - Assert.AreEqual(0, r.depth, "- 0 0 0 D"); - } - - [Test] - public void Subtract() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 r = Vector3.zero; - - r = v1 - v2; - Assert.IsTrue(r == new Vector3(3, 3, 3), "4 5 6 - 1 2 3"); - - v2 = new(-1, -2, -3); - r = v1 - v2; - Assert.IsTrue(r == new Vector3(5, 7, 9), "4 5 6 - -1 -2 -3"); - - v2 = new(4, 5, 6); - r = v1 - v2; - Assert.IsTrue(r == new Vector3(0, 0, 0), "4 5 6 - 4 5 6"); - r = v1; - r -= v2; - Assert.AreEqual(r, new Vector3(0, 0, 0), "4 5 6 - 4 5 6"); - - v2 = new(0, 0, 0); - r = v1 - v2; - Assert.AreEqual(r, new Vector3(4, 5, 6), "4 5 6 - 0 0 0"); - r -= v2; - Assert.AreEqual(r, new Vector3(4, 5, 6), "4 5 6 - 0 0 0"); - - // Infinity tests are still missing - } - - [Test] - public void Addition() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 r = Vector3.zero; - - r = v1 + v2; - Assert.IsTrue(r == new Vector3(5, 7, 9), "4 5 6 + 1 2 3"); - - v2 = new(-1, -2, -3); - r = v1 + v2; - Assert.IsTrue(r == new Vector3(3, 3, 3), "4 5 6 + -1 -2 -3"); - r = v1; - r += v2; - Assert.AreEqual(r, new Vector3(3, 3, 3), "4 5 6 + -1 -2 -3"); - - v2 = new(0, 0, 0); - r = v1 + v2; - Assert.AreEqual(r, new Vector3(4, 5, 6), "4 5 6 + 0 0 0"); - r += v2; - Assert.AreEqual(r, new Vector3(4, 5, 6), "4 5 6 + 0 0 0"); - - // Infinity tests are still missing - } - - [Test] - public void Scale() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 r; - - r = Vector3.Scale(v1, v2); - Assert.AreEqual(4, r.horizontal, "Scale 4 5 6 , 1 2 3 H"); - Assert.AreEqual(10, r.vertical, "Scale 4 5 6 , 1 2 3 V"); - Assert.AreEqual(18, r.depth, "Scale 4 5 6 , 1 2 3 D"); - - v2 = new(-1, -2, -3); - r = Vector3.Scale(v1, v2); - Assert.AreEqual(-4, r.horizontal, "Scale 4 5 6 , -1 -2 -3 H"); - Assert.AreEqual(-10, r.vertical, "Scale 4 5 6 , -1 -2 -3 V"); - Assert.AreEqual(-18, r.depth, "Scale 4 5 6 , -1 -2 -3 D"); - - v2 = new(0, 0, 0); - r = Vector3.Scale(v1, v2); - Assert.AreEqual(0, r.horizontal, "Scale 4 5 6 , 0 0 0 H"); - Assert.AreEqual(0, r.vertical, "Scale 4 5 6 , 0 0 0 V"); - Assert.AreEqual(0, r.depth, "Scale 4 5 6 , 0 0 0 D"); - } - - [Test] - public void Multiply() { - Vector3 v1 = new(4, 5, 6); - int f = 3; - Vector3 r; - - r = v1 * f; - Assert.AreEqual(12, r.horizontal, "4 5 6 * 3 H"); - Assert.AreEqual(15, r.vertical, "4 5 6 * 3 V"); - Assert.AreEqual(18, r.depth, "4 5 6 * 3 D"); - - r = f * v1; - Assert.AreEqual(12, r.horizontal, "3 * 4 5 6 H"); - Assert.AreEqual(15, r.vertical, "3 * 4 5 6 V"); - Assert.AreEqual(18, r.depth, "3 * 4 5 6 D"); - - f = -3; - r = v1 * f; - Assert.AreEqual(-12, r.horizontal, "4 5 6 * -3 H"); - Assert.AreEqual(-15, r.vertical, "4 5 6 * -3 V"); - Assert.AreEqual(-18, r.depth, "4 5 6 * -3 D"); - - f = 0; - r = v1 * f; - Assert.AreEqual(0, r.horizontal, "4 5 6 * 0 H"); - Assert.AreEqual(0, r.vertical, "4 5 6 * 0 V"); - Assert.AreEqual(0, r.depth, "4 5 6 * 0 D"); - } - - [Test] - public void Divide() { - Vector3 v1 = new(4, 5, 6); - int f = 2; - Vector3 r; - - r = v1 / f; - Assert.AreEqual(2, r.horizontal, "4 5 6 / 2 H"); - Assert.AreEqual(2, r.vertical, "4 5 6 / 2 V"); - Assert.AreEqual(3, r.depth, "4 5 6 / 2 D"); - - f = -2; - r = v1 / f; - Assert.AreEqual(-2, r.horizontal, "4 5 6 / -2 H"); - Assert.AreEqual(-2, r.vertical, "4 5 6 / -2 V"); - Assert.AreEqual(-3, r.depth, "4 5 6 / -2 D"); - - Assert.Throws(() => { - f = 0; - r = v1 / f; - }); - } - - [Test] - public void Dot() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - float f; - - f = Vector3.Dot(v1, v2); - Assert.AreEqual(32, f, "Dot(4 5 6, 1 2 3)"); - - v2 = new(-1, -2, -3); - f = Vector3.Dot(v1, v2); - Assert.AreEqual(-32, f, "Dot(4 5 6, -1 -2 -3)"); - - v2 = new(0, 0, 0); - f = Vector3.Dot(v1, v2); - Assert.AreEqual(0, f, "Dot(4 5 6, 0 0 0)"); - } - - [Test] - public void Cross() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 r; - - r = Vector3.Cross(v1, v2); - Assert.AreEqual(3, r.horizontal, "Cross(4 5 6, 1 2 3) H"); - Assert.AreEqual(-6, r.vertical, "Cross(4 5 6, 1 2 3) V"); - Assert.AreEqual(3, r.depth, "Cross(4 5 6, 1 2 3) D"); - - v2 = new(-1, -2, -3); - r = Vector3.Cross(v1, v2); - Assert.AreEqual(-3, r.horizontal, "Cross(4 5 6, -1 -2 -3) H"); - Assert.AreEqual(6, r.vertical, "Cross(4 5 6, -1 -2 -3) V"); - Assert.AreEqual(-3, r.depth, "Cross(4 5 6, -1 -2 -3) D"); - - v2 = new(0, 0, 0); - r = Vector3.Cross(v1, v2); - Assert.AreEqual(0, r.horizontal, "Cross(4 5 6, 0 0 0) H"); - Assert.AreEqual(0, r.vertical, "Cross(4 5 6, 0 0 0) V"); - Assert.AreEqual(0, r.depth, "Cross(4 5 6, 0 0 0) D"); - } - - [Test] - public void UnsignedAngle() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - AngleFloat a; - - a = Vector3.UnsignedAngle(v1, v2); - Assert.AreEqual(12.9331379F, a.inDegrees, "Angle(4 5 6, 1 2 3)"); - - v2 = new(-1, -2, -3); - a = Vector3.UnsignedAngle(v1, v2); - Assert.AreEqual(167.066849F, a.inDegrees, "Angle(4 5 6, -1 -2 -3)"); - - v2 = new(0, 0, 0); - a = Vector3.UnsignedAngle(v1, v2); - Assert.AreEqual(0, a.inDegrees, "Angle(4 5 6, 0 0 0)"); - } - - [Test] - public void SignedAngle() { - Vector3 v1 = new(4, 5, 6); - Vector3 v2 = new(1, 2, 3); - Vector3 v3 = new(7, 8, -9); - AngleFloat a; - - a = Vector3.SignedAngle(v1, v2, v3); - Assert.AreEqual(-12.9331379F, a.inDegrees, "SignedAngle(4 5 6, 1 2 3, 7 8 -9)"); - - v2 = new(-1, -2, -3); - a = Vector3.SignedAngle(v1, v2, v3); - Assert.AreEqual(167.066849F, a.inDegrees, "SignedAngle(4 5 6, -1 -2 -3, 7 8 -9)"); - - v2 = new(0, 0, 0); - a = Vector3.SignedAngle(v1, v2, v3); - Assert.AreEqual(0, a.inDegrees, "SignedAngle(4 5 6, 0 0 0, 7 8 -9)"); - - v2 = new(1, 2, 3); - v3 = new(-7, -8, 9); - a = Vector3.SignedAngle(v1, v2, v3); - Assert.AreEqual(12.9331379F, a.inDegrees, "SignedAngle(4 5 6, 1 2 3, -7 -8 9)"); - - v3 = new(0, 0, 0); - a = Vector3.SignedAngle(v1, v2, v3); - Assert.AreEqual(0, a.inDegrees, "SignedAngle(4 5 6, 1 2 3, 0 0 0)"); - } - } -} -#endif \ No newline at end of file diff --git a/NanoBrain/MemoryCell.cs b/NanoBrain/MemoryCell.cs deleted file mode 100644 index 7b7b8e5..0000000 --- a/NanoBrain/MemoryCell.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using Unity.Mathematics; - -[Serializable] -public class MemoryCell : Neuron { - - public MemoryCell(ClusterPrefab cluster, string name) : base(cluster, name) { } - public MemoryCell(Cluster parent, string name) : base(parent, name) { } - - public bool staticMemory = false; - public override bool isSleeping { - get { - if (staticMemory) - return false; - - return base.isSleeping; - } - } - - public override Nucleus ShallowCloneTo(Cluster newParent) { - MemoryCell clone = new(newParent, this.name); - CloneFields(clone); - clone.staticMemory = this.staticMemory; - return clone; - } - - #region State - - private bool initialized = false; - - private float3 _memorizedValue; - - public override void UpdateStateIsolated() { - // A memorycell does not have an activation function - float3 result = Combinator(); - - if (initialized) - // Output the previous, memorized value - this.outputValue = this._memorizedValue; - else { - // The first time, the result is directly set in output - this.outputValue = result; - this.initialized = true; - } - - // Store the result for the next time - this._memorizedValue = result; - } - - public override void UpdateNuclei() { - if (staticMemory) - // Static memory does not get stale or go to sleep - return; - - base.UpdateNuclei(); - } - - #endregion State -} diff --git a/NanoBrain/MemoryCell.cs.meta b/NanoBrain/MemoryCell.cs.meta deleted file mode 100644 index 24dc14d..0000000 --- a/NanoBrain/MemoryCell.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 29633aa3fe5cd9dcc8d886051f45d4d8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/NanoBrain-Unity.code-workspace b/NanoBrain/NanoBrain-Unity.code-workspace deleted file mode 100644 index 5194438..0000000 --- a/NanoBrain/NanoBrain-Unity.code-workspace +++ /dev/null @@ -1,12 +0,0 @@ -{ - "folders": [ - { - "path": "../.." - }, - { - "name": "LinearAlgebra-csharp", - "path": "LinearAlgebra-csharp" - } - ], - "settings": {} -} \ No newline at end of file diff --git a/NanoBrain/NanoBrain-Unity.code-workspace.meta b/NanoBrain/NanoBrain-Unity.code-workspace.meta deleted file mode 100644 index 65bb132..0000000 --- a/NanoBrain/NanoBrain-Unity.code-workspace.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: cfec45da5945b94d684a763d86b0dcf8 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/NanoBrain.cs b/NanoBrain/NanoBrain.cs deleted file mode 100644 index 5a7525e..0000000 --- a/NanoBrain/NanoBrain.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using UnityEngine; - -public class NanoBrain : MonoBehaviour { - public ClusterPrefab defaultBrain; - - [NonSerialized] - private Cluster brainInstance; - public Cluster brain { - get { - if (brainInstance == null && defaultBrain != null) { - brainInstance = new Cluster(defaultBrain) { - name = defaultBrain.name + " (Instance)" - }; - } - return brainInstance; - } - } - - public static void UpdateWeight(Cluster brain, string name, float weight) { - Nucleus root = brain.defaultOutput; - foreach (Synapse synapse in root.synapses) { - if (synapse.neuron.name == name) { - if (synapse.weight != weight) { - synapse.weight = weight; - // Debug.Log($"Updated weight for {name}"); - } - } - } - } -} \ No newline at end of file diff --git a/NanoBrain/NanoBrain.cs.meta b/NanoBrain/NanoBrain.cs.meta deleted file mode 100644 index fc8b1c9..0000000 --- a/NanoBrain/NanoBrain.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 92f34a5e4027a1dc39efd8ce63cf6aba -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Neuron.cs b/NanoBrain/Neuron.cs deleted file mode 100644 index 3ca76c4..0000000 --- a/NanoBrain/Neuron.cs +++ /dev/null @@ -1,336 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; -using UnityEditor; -#if UNITY_MATHEMATICS -using Unity.Mathematics; -using static Unity.Mathematics.math; -#endif - -[Serializable] -public class Neuron : Nucleus { - - public Neuron(Cluster parent, string name) { - this.parent = parent; - this.name = name; - this.parent?.clusterNuclei.Add(this); - } - public Neuron(ClusterPrefab prefab, string name) { - this.clusterPrefab = prefab; - this.name = name; - if (this.clusterPrefab != null) - this.clusterPrefab.nuclei.Add(this); - else - Debug.LogError("No prefab when adding neuron to prefab"); - } - - #region Serialization - - public enum CombinatorType { - Sum, - Product, - Max - } - public CombinatorType combinator = CombinatorType.Sum; - - public enum CurvePresets { - Linear, - Power, - Sqrt, - Reciprocal, - Custom - } - [SerializeField] - public CurvePresets _curvePreset; - public CurvePresets curvePreset { - get { return _curvePreset; } - set { - _curvePreset = value; - this.curve = GenerateCurve(); - } - } - public AnimationCurve curve; - public float curveMax = 1.0f; - - public AnimationCurve GenerateCurve() { - switch (this.curvePreset) { - case CurvePresets.Linear: - this.curveMax = 1; - return Presets.Linear(1); - case CurvePresets.Power: - this.curveMax = 1; - return Presets.Power(2.0f, 1); - case CurvePresets.Sqrt: - this.curveMax = 1; - return Presets.Power(0.5f, 1); - case CurvePresets.Reciprocal: - this.curveMax = 1 / 0.01f * 1; - return Presets.Reciprocal(1); - default: - this.curveMax = 1; - return this.curve; - } - } - - public static class Presets { - private const int samples = 32; - public static AnimationCurve Linear(float weight) { - return AnimationCurve.Linear(0f, 0f, 1000f, weight * 1000); - } - public static AnimationCurve Power(float exponent, float weight) { - // build keyframes - Keyframe[] keys = new Keyframe[samples]; - for (int i = 0; i < samples; i++) { - float t = i / (float)(samples - 1); - float v = Mathf.Pow(t, exponent) * weight; - keys[i] = new Keyframe(t, v); - } - - AnimationCurve curve = new(keys); - - // set tangent modes for each key to Auto (smooth). Use Linear if you prefer straight segments. - for (int i = 0; i < curve.length; i++) { - AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Auto); - AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Auto); - } - - return curve; - } - public static AnimationCurve Reciprocal(float weight) { - int samples = 128; - float xMin = 0.001f; - float xMax = 1; - var keys = new Keyframe[samples]; - for (int i = 0; i < samples; i++) { - float t = i / (float)(samples - 1); - float x = Mathf.Lerp(xMin, xMax, t); - float y = 1f / x * weight; - keys[i] = new Keyframe(x, y); - } - var curve = new AnimationCurve(keys); - for (int i = 0; i < curve.length; i++) { - AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Linear); - AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Linear); - } - return curve; - } - } - - #endregion Serialization - - protected float3 _outputValue; - public virtual float3 outputValue { - get { return _outputValue; } - set { - _outputValue = value; - if (this.isFiring) - WhenFiring?.Invoke(); - } - } - public bool isFiring => length(_outputValue) > 0.5f; - public Action WhenFiring; - - public virtual bool isSleeping => lengthsq(this.outputValue) == 0; - [NonSerialized] - public int stale = 1000; - public readonly int staleValueForSleep = 20; - - // this clone the nucleus without the synapses and receivers - public override Nucleus ShallowCloneTo(Cluster newParent) { - Neuron clone = new(newParent, this.name); - CloneFields(clone); - return clone; - } - - public override Nucleus Clone(ClusterPrefab prefab) { - Neuron clone = new(prefab, this.name); - CloneFields(clone); - foreach (Synapse synapse in this.synapses) { - Synapse clonedSynapse = clone.AddSynapse(synapse.neuron); - clonedSynapse.weight = synapse.weight; - } - foreach (Nucleus receiver in this.receivers) { - clone.AddReceiver(receiver); - } - return clone; - } - - protected virtual void CloneFields(Neuron clone) { - clone.clusterPrefab = this.clusterPrefab; - clone.bias = this.bias; - clone.combinator = this.combinator; - clone.curve = this.curve; - clone.curvePreset = this.curvePreset; - clone.curveMax = this.curveMax; - } - - public static void Delete(Nucleus nucleus) { - foreach (Synapse synapse in nucleus.synapses) { - if (synapse.neuron is Neuron synapse_nucleus) { - if (synapse_nucleus.receivers.Count > 1) { - // there is another nucleus feeding into this input nucleus - synapse_nucleus.receivers.RemoveAll(r => r == nucleus); - } - else { - // No other links, delete it. - Neuron.Delete(synapse_nucleus); - } - } - } - if (nucleus is Neuron neuron) { - foreach (Nucleus receiver in neuron.receivers) { - if (receiver != null && receiver.synapses != null) - receiver.synapses.RemoveAll(s => s.neuron == nucleus); - } - } - else if (nucleus is Cluster cluster) { - // remove all receivers for this cluster - foreach (Neuron output in cluster.outputs) { - foreach (Nucleus receiver in output.receivers) { - receiver.synapses.RemoveAll(s => s.neuron == output); - } - } - } - - - if (nucleus.clusterPrefab != null) { - nucleus.clusterPrefab.nuclei.RemoveAll(n => n == nucleus); - nucleus.clusterPrefab.RefreshOutputs(); - nucleus.clusterPrefab.GarbageCollection(); - } - } - - public override void UpdateStateIsolated() { - float3 result = Combinator(); - this.outputValue = Activator(result); - } - - #region Combinator - - protected Func Combinator => combinator switch { - CombinatorType.Sum => CombinatorSum, - CombinatorType.Product => CombinatorProduct, - CombinatorType.Max => CombinatorMax, - _ => CombinatorSum - }; - - public float3 CombinatorSum() { - float3 sum = this.bias; - foreach (Synapse synapse in this.synapses) - sum += synapse.weight * synapse.neuron.outputValue; - return sum; - } - - public float3 CombinatorProduct() { - float3 product = this.bias; - foreach (Synapse synapse in this.synapses) { - product *= synapse.weight * synapse.neuron.outputValue; - } - return product; - } - - public float3 CombinatorMax() { - float3 max = this.bias; - float maxLength = length(max); - - //Applying the weight factors - foreach (Synapse synapse in this.synapses) { - float3 input = synapse.weight * synapse.neuron.outputValue; - - float inputLength = length(input); - if (inputLength > maxLength) { - max = input; - maxLength = inputLength; - } - } - return max; - } - - #endregion Combinator - - #region Activator - - public Func Activator => this.curvePreset switch { - CurvePresets.Linear => ActivatorLinear, - CurvePresets.Sqrt => ActivatorSqrt, - CurvePresets.Power => ActivatorPower, - CurvePresets.Reciprocal => ActivatorReciprocal, - _ => ActivatorCustom - }; - - protected float3 ActivatorLinear(float3 input) { - return input; - } - - protected float3 ActivatorSqrt(float3 input) { - float3 result = normalize(input) * System.MathF.Sqrt(length(input)); - return result; - } - - protected float3 ActivatorPower(float3 input) { - float3 result = normalize(input) * System.MathF.Pow(length(input), 2); - return result; - } - - protected float3 ActivatorReciprocal(float3 input) { - float magnitude = length(input); - if (magnitude == 0) - return new float3(0, 0, 0); - - float3 result = normalize(input) * (1 / magnitude); - return result; - } - - protected float3 ActivatorCustom(float3 input) { - float activatedValue = this.curve.Evaluate(length(input)); - float3 result = normalize(input) * activatedValue; - return result; - } - - #endregion Activator - - #region Receivers - - [SerializeReference] - private List _receivers = new(); - public virtual List receivers { - get { return _receivers; } - set { _receivers = value; } - } - - public virtual void AddReceiver(Nucleus receiverToAdd, float weight = 1) { - this._receivers.Add(receiverToAdd); - receiverToAdd.AddSynapse(this, weight); - } - - public virtual void RemoveReceiver(Nucleus receiverToRemove) { - if (this is IReceptor receptor) { - foreach (Nucleus element in receptor.nucleiArray) { - if (element is Neuron neuron) { - neuron._receivers.RemoveAll(receiver => receiver == receiverToRemove); - receiverToRemove.synapses.RemoveAll(synapse => synapse.neuron == neuron); - } - } - } - else { - this._receivers.RemoveAll(receiver => receiver == receiverToRemove); - receiverToRemove.synapses.RemoveAll(synapse => synapse.neuron == this); - } - } - - - #endregion Receivers - - public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) { - if (this.parent is ClusterReceptor clusterReceptor) - clusterReceptor.ProcessStimulus(this, inputValue, thingId, thingName); - else - ProcessStimulusDirect(inputValue, thingId, thingName); - } - - public void ProcessStimulusDirect(Vector3 inputValue, int thingId = 0, string thingName = null) { - this.stale = 0; - this.bias = inputValue; - this.parent.UpdateFromNucleus(this); - } -} \ No newline at end of file diff --git a/NanoBrain/Neuron.cs.meta b/NanoBrain/Neuron.cs.meta deleted file mode 100644 index 37266c0..0000000 --- a/NanoBrain/Neuron.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 750748f3f0e7d472fbf88ab02987074c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/NewVelocity.asset b/NanoBrain/NewVelocity.asset deleted file mode 100644 index 87c56b4..0000000 --- a/NanoBrain/NewVelocity.asset +++ /dev/null @@ -1,1305 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 60a957541c24c57e78018c202ebb1d9b, type: 3} - m_Name: NewVelocity - m_EditorClassIdentifier: Assembly-CSharp::ClusterPrefab - nuclei: - - rid: 2262690579536937007 - - rid: 2262690579536937008 - - rid: 2262690579536937009 - - rid: 2262690579536937010 - references: - version: 2 - RefIds: - - rid: -2 - type: {class: , ns: , asm: } - - rid: 2262690579536937007 - type: {class: Neuron, ns: , asm: Assembly-CSharp} - data: - name: Proximity - clusterPrefab: {fileID: 11400000} - parent: - rid: -2 - trace: 0 - bias: {x: 0, y: 0, z: 0} - _synapses: - - nucleus: - rid: 2262690579536937008 - weight: 1 - combinator: 0 - _curvePreset: 3 - curve: - serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0.001 - value: 999.99994 - inSlope: 0 - outSlope: -112788.63 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.008866142 - value: 112.788635 - inSlope: -112788.63 - outSlope: -6740.78 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.016732283 - value: 59.76471 - inSlope: -6740.78 - outSlope: -2429.6155 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.024598425 - value: 40.653008 - inSlope: -2429.6155 - outSlope: -1252.2269 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.032464568 - value: 30.802813 - inSlope: -1252.2269 - outSlope: -763.7558 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.040330708 - value: 24.795002 - inSlope: -763.7558 - outSlope: -514.45264 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.04819685 - value: 20.748245 - inSlope: -514.45264 - outSlope: -370.0882 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.056062993 - value: 17.837078 - inSlope: -370.0882 - outSlope: -279.01324 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.06392913 - value: 15.642321 - inSlope: -279.01324 - outSlope: -217.87398 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.07179528 - value: 13.928493 - inSlope: -217.87398 - outSlope: -174.8461 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.079661414 - value: 12.553129 - inSlope: -174.8461 - outSlope: -143.41913 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.087527566 - value: 11.424973 - inSlope: -143.41913 - outSlope: -119.76661 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.0953937 - value: 10.482872 - inSlope: -119.76661 - outSlope: -101.519356 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.10325985 - value: 9.684306 - inSlope: -101.519356 - outSlope: -87.14706 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.11112598 - value: 8.9987955 - inSlope: -87.14706 - outSlope: -75.62513 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.11899213 - value: 8.403917 - inSlope: -75.62513 - outSlope: -66.24654 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.12685826 - value: 7.882813 - inSlope: -66.24654 - outSlope: -58.510654 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.13472441 - value: 7.4225597 - inSlope: -58.510654 - outSlope: -52.055042 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.14259055 - value: 7.0130873 - inSlope: -52.055042 - outSlope: -46.612007 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.1504567 - value: 6.6464305 - inSlope: -46.612007 - outSlope: -41.98024 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.15832284 - value: 6.316208 - inSlope: -41.98024 - outSlope: -38.006134 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.16618897 - value: 6.0172467 - inSlope: -38.006134 - outSlope: -34.570965 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.17405513 - value: 5.745306 - inSlope: -34.570965 - outSlope: -31.581244 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.18192126 - value: 5.496884 - inSlope: -31.581244 - outSlope: -28.963417 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.1897874 - value: 5.2690535 - inSlope: -28.963417 - outSlope: -26.658009 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.19765353 - value: 5.059358 - inSlope: -26.658009 - outSlope: -24.617418 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.20551969 - value: 4.8657136 - inSlope: -24.617418 - outSlope: -22.802412 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.21338584 - value: 4.6863465 - inSlope: -22.802412 - outSlope: -21.181019 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.22125196 - value: 4.519734 - inSlope: -21.181019 - outSlope: -19.72667 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.22911811 - value: 4.364561 - inSlope: -19.72667 - outSlope: -18.417059 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.23698425 - value: 4.21969 - inSlope: -18.417059 - outSlope: -17.233776 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.2448504 - value: 4.0841265 - inSlope: -17.233776 - outSlope: -16.160883 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.25271654 - value: 3.9570026 - inSlope: -16.160883 - outSlope: -15.185221 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.2605827 - value: 3.8375535 - inSlope: -15.185221 - outSlope: -14.295299 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.2684488 - value: 3.725105 - inSlope: -14.295299 - outSlope: -13.481375 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.27631494 - value: 3.6190586 - inSlope: -13.481375 - outSlope: -12.735047 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.28418112 - value: 3.5188825 - inSlope: -12.735047 - outSlope: -12.04901 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.29204726 - value: 3.4241033 - inSlope: -12.04901 - outSlope: -11.416967 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.2999134 - value: 3.3342957 - inSlope: -11.416967 - outSlope: -10.8334 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.30777952 - value: 3.249079 - inSlope: -10.8334 - outSlope: -10.293426 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.31564566 - value: 3.1681094 - inSlope: -10.293426 - outSlope: -9.792865 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.3235118 - value: 3.0910773 - inSlope: -9.792865 - outSlope: -9.327949 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.33137795 - value: 3.0177023 - inSlope: -9.327949 - outSlope: -8.895375 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.33924407 - value: 2.9477303 - inSlope: -8.895375 - outSlope: -8.492224 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.34711024 - value: 2.880929 - inSlope: -8.492224 - outSlope: -8.115812 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.3549764 - value: 2.8170888 - inSlope: -8.115812 - outSlope: -7.76395 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.36284253 - value: 2.7560165 - inSlope: -7.76395 - outSlope: -7.434456 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.37070867 - value: 2.697536 - inSlope: -7.434456 - outSlope: -7.1255083 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.3785748 - value: 2.641486 - inSlope: -7.1255083 - outSlope: -6.8354197 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.38644093 - value: 2.5877175 - inSlope: -6.8354197 - outSlope: -6.562695 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.39430708 - value: 2.5360944 - inSlope: -6.562695 - outSlope: -6.305974 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.40217322 - value: 2.4864907 - inSlope: -6.305974 - outSlope: -6.064021 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.4100394 - value: 2.43879 - inSlope: -6.064021 - outSlope: -5.835745 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.4179055 - value: 2.3928854 - inSlope: -5.835745 - outSlope: -5.6201315 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.42577165 - value: 2.3486767 - inSlope: -5.6201315 - outSlope: -5.4162097 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.4336378 - value: 2.306072 - inSlope: -5.4162097 - outSlope: -5.223229 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.44150394 - value: 2.2649853 - inSlope: -5.223229 - outSlope: -5.040342 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.4493701 - value: 2.2253373 - inSlope: -5.040342 - outSlope: -4.8669295 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.4572362 - value: 2.1870534 - inSlope: -4.8669295 - outSlope: -4.7023005 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.46510234 - value: 2.1500645 - inSlope: -4.7023005 - outSlope: -4.5458865 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.47296852 - value: 2.1143057 - inSlope: -4.5458865 - outSlope: -4.3971753 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.48083466 - value: 2.079717 - inSlope: -4.3971753 - outSlope: -4.2555995 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.4887008 - value: 2.0462418 - inSlope: -4.2555995 - outSlope: -4.1207685 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.49656692 - value: 2.0138273 - inSlope: -4.1207685 - outSlope: -3.9922712 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.5044331 - value: 1.9824234 - inSlope: -3.9922712 - outSlope: -3.8696532 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.5122992 - value: 1.9519844 - inSlope: -3.8696532 - outSlope: -3.7526293 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.5201653 - value: 1.9224657 - inSlope: -3.7526293 - outSlope: -3.6408176 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.52803147 - value: 1.8938265 - inSlope: -3.6408176 - outSlope: -3.5339315 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.5358976 - value: 1.8660281 - inSlope: -3.5339315 - outSlope: -3.4316826 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.54376376 - value: 1.839034 - inSlope: -3.4316826 - outSlope: -3.3338284 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.5516299 - value: 1.8128096 - inSlope: -3.3338284 - outSlope: -3.240066 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.55949605 - value: 1.7873228 - inSlope: -3.240066 - outSlope: -3.1502352 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.56736225 - value: 1.7625424 - inSlope: -3.1502352 - outSlope: -3.0640743 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.5752284 - value: 1.7384399 - inSlope: -3.0640743 - outSlope: -2.9814053 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.58309454 - value: 1.7149878 - inSlope: -2.9814053 - outSlope: -2.9020314 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.5909606 - value: 1.6921601 - inSlope: -2.9020314 - outSlope: -2.8257964 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.59882677 - value: 1.669932 - inSlope: -2.8257964 - outSlope: -2.7525082 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.6066929 - value: 1.6482804 - inSlope: -2.7525082 - outSlope: -2.6820538 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.61455905 - value: 1.627183 - inSlope: -2.6820538 - outSlope: -2.6142666 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.6224252 - value: 1.6066188 - inSlope: -2.6142666 - outSlope: -2.5490105 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.63029134 - value: 1.5865679 - inSlope: -2.5490105 - outSlope: -2.4861636 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.6381575 - value: 1.5670114 - inSlope: -2.4861636 - outSlope: -2.4256358 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.64602363 - value: 1.547931 - inSlope: -2.4256358 - outSlope: -2.3672597 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.6538898 - value: 1.5293097 - inSlope: -2.3672597 - outSlope: -2.3109925 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.66175586 - value: 1.5111313 - inSlope: -2.3109925 - outSlope: -2.2566907 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.669622 - value: 1.4933798 - inSlope: -2.2566907 - outSlope: -2.2042859 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.67748815 - value: 1.4760406 - inSlope: -2.2042859 - outSlope: -2.1536992 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.6853543 - value: 1.4590993 - inSlope: -2.1536992 - outSlope: -2.1048093 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.6932205 - value: 1.4425424 - inSlope: -2.1048093 - outSlope: -2.0575728 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.70108664 - value: 1.4263573 - inSlope: -2.0575728 - outSlope: -2.011927 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.7089528 - value: 1.4105312 - inSlope: -2.011927 - outSlope: -1.9677659 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.7168189 - value: 1.3950524 - inSlope: -1.9677659 - outSlope: -1.9250447 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.7246851 - value: 1.3799098 - inSlope: -1.9250447 - outSlope: -1.8837026 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.7325512 - value: 1.3650923 - inSlope: -1.8837026 - outSlope: -1.8436778 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.7404173 - value: 1.3505898 - inSlope: -1.8436778 - outSlope: -1.8049132 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.74828345 - value: 1.336392 - inSlope: -1.8049132 - outSlope: -1.7673749 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.7561496 - value: 1.3224896 - inSlope: -1.7673749 - outSlope: -1.7309732 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.76401573 - value: 1.3088735 - inSlope: -1.7309732 - outSlope: -1.695693 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.7718819 - value: 1.295535 - inSlope: -1.695693 - outSlope: -1.6614736 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.779748 - value: 1.2824656 - inSlope: -1.6614736 - outSlope: -1.6282848 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.78761417 - value: 1.2696573 - inSlope: -1.6282848 - outSlope: -1.5960962 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.7954803 - value: 1.2571021 - inSlope: -1.5960962 - outSlope: -1.564832 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.80334646 - value: 1.2447929 - inSlope: -1.564832 - outSlope: -1.5344887 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.81121254 - value: 1.2327225 - inSlope: -1.5344887 - outSlope: -1.5050048 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.81907874 - value: 1.2208838 - inSlope: -1.5050048 - outSlope: -1.4763738 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.8269449 - value: 1.2092705 - inSlope: -1.4763738 - outSlope: -1.4485649 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.83481103 - value: 1.1978759 - inSlope: -1.4485649 - outSlope: -1.4215137 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.8426772 - value: 1.186694 - inSlope: -1.4215137 - outSlope: -1.3952202 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.8505433 - value: 1.175719 - inSlope: -1.3952202 - outSlope: -1.369639 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.85840946 - value: 1.1649452 - inSlope: -1.369639 - outSlope: -1.3447852 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.8662756 - value: 1.154367 - inSlope: -1.3447852 - outSlope: -1.320568 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.87414175 - value: 1.1439792 - inSlope: -1.320568 - outSlope: -1.2970176 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.8820079 - value: 1.1337767 - inSlope: -1.2970176 - outSlope: -1.2740829 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.889874 - value: 1.1237546 - inSlope: -1.2740829 - outSlope: -1.2517655 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.8977401 - value: 1.113908 - inSlope: -1.2517655 - outSlope: -1.2300034 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.90560627 - value: 1.1042327 - inSlope: -1.2300034 - outSlope: -1.2088321 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.9134724 - value: 1.0947238 - inSlope: -1.2088321 - outSlope: -1.1881914 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.92133856 - value: 1.0853773 - inSlope: -1.1881914 - outSlope: -1.1680659 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.9292047 - value: 1.0761892 - inSlope: -1.1680659 - outSlope: -1.1484709 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.93707085 - value: 1.0671551 - inSlope: -1.1484709 - outSlope: -1.1293371 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.94493705 - value: 1.0582715 - inSlope: -1.1293371 - outSlope: -1.1106901 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.9528032 - value: 1.0495347 - inSlope: -1.1106901 - outSlope: -1.0925045 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.96066934 - value: 1.0409409 - inSlope: -1.0925045 - outSlope: -1.0747513 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.9685354 - value: 1.0324868 - inSlope: -1.0747513 - outSlope: -1.0574516 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.97640157 - value: 1.0241687 - inSlope: -1.0574516 - outSlope: -1.0405389 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.9842677 - value: 1.0159837 - inSlope: -1.0405389 - outSlope: -1.0240355 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 0.99213386 - value: 1.0079285 - inSlope: -1.0240355 - outSlope: -1.0079259 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 1 - value: 1 - inSlope: -1.0079259 - outSlope: 0 - tangentMode: 69 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 - curveMax: 100 - _receivers: [] - - rid: 2262690579536937008 - type: {class: Neuron, ns: , asm: Assembly-CSharp} - data: - name: Position - clusterPrefab: {fileID: 11400000} - parent: - rid: -2 - trace: 0 - bias: {x: 0, y: 0, z: 0} - _synapses: [] - combinator: 0 - _curvePreset: 0 - curve: - serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0 - value: 0 - inSlope: 0 - outSlope: 1 - tangentMode: 0 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 1000 - value: 1000 - inSlope: 1 - outSlope: 0 - tangentMode: 0 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 - curveMax: 1 - _receivers: - - rid: 2262690579536937007 - - rid: 2262690579536937009 - - rid: 2262690579536937009 - type: {class: Neuron, ns: , asm: Assembly-CSharp} - data: - name: Velocity - clusterPrefab: {fileID: 11400000} - parent: - rid: -2 - trace: 0 - bias: {x: 0, y: 0, z: 0} - _synapses: - - nucleus: - rid: 2262690579536937008 - weight: 1 - - nucleus: - rid: 2262690579536937010 - weight: 1 - combinator: 0 - _curvePreset: 0 - curve: - serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0 - value: 0 - inSlope: 0 - outSlope: 1 - tangentMode: 0 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 1000 - value: 1000 - inSlope: 1 - outSlope: 0 - tangentMode: 0 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 - curveMax: 1 - _receivers: [] - - rid: 2262690579536937010 - type: {class: MemoryCell, ns: , asm: Assembly-CSharp} - data: - name: New memory cell - clusterPrefab: {fileID: 11400000} - parent: - rid: -2 - trace: 0 - bias: {x: 0, y: 0, z: 0} - _synapses: [] - combinator: 0 - _curvePreset: 0 - curve: - serializedVersion: 2 - m_Curve: [] - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 - curveMax: 1 - _receivers: - - rid: 2262690579536937009 - staticMemory: 0 diff --git a/NanoBrain/NewVelocity.asset.meta b/NanoBrain/NewVelocity.asset.meta deleted file mode 100644 index 5718f8b..0000000 --- a/NanoBrain/NewVelocity.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 61eea9f818639ec20b7a7bf4e86fff66 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Nucleus.cs b/NanoBrain/Nucleus.cs deleted file mode 100644 index 2b1f5da..0000000 --- a/NanoBrain/Nucleus.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -[Serializable] -public abstract class Nucleus { - public string name; - - [SerializeReference] - public ClusterPrefab clusterPrefab; - [SerializeReference] - public Cluster parent; - - public bool trace = false; - - public abstract Nucleus ShallowCloneTo(Cluster parent); - public abstract Nucleus Clone(ClusterPrefab prefab); - - public enum Type { - None, - Neuron, - MemoryCell, - Cluster, - Receptor, - ClusterReceptor, - } - - #region Synapses - - public Vector3 bias = Vector3.zero; - - [SerializeField] - private List _synapses = new(); - public List synapses => _synapses; - - public Synapse AddSynapse(Neuron sendingNucleus, float weight = 1.0f) { - Synapse synapse = new(sendingNucleus, weight); - this.synapses.Add(synapse); - return synapse; - } - - public Synapse GetSynapse(Nucleus sender) { - foreach (Synapse synapse in this.synapses) - if (synapse.neuron == sender) - return synapse; - return null; - } - - public void RemoveSynapse(Nucleus sendingNucleus) { - this.synapses.RemoveAll(synapse => synapse.neuron == sendingNucleus); - } - - #endregion Synapses - - #region Update - - public abstract void UpdateStateIsolated(); - - public virtual void UpdateNuclei() { - } - - public virtual void SetBias(Vector3 inputValue) { - this.bias = inputValue; - this.parent.UpdateFromNucleus(this); - } - - public virtual void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = "") { - } - - #endregion Update - -} \ No newline at end of file diff --git a/NanoBrain/Nucleus.cs.meta b/NanoBrain/Nucleus.cs.meta deleted file mode 100644 index 0868066..0000000 --- a/NanoBrain/Nucleus.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4310eea6ab77628b085387a226c1c386 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/NucleusArray.cs b/NanoBrain/NucleusArray.cs deleted file mode 100644 index 9f8a172..0000000 --- a/NanoBrain/NucleusArray.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System.Linq; -using System.Collections.Generic; -using UnityEngine; -using Unity.Mathematics; -using static Unity.Mathematics.math; - -[System.Serializable] -public class NucleusArray { - [SerializeReference] - private Nucleus[] _nuclei; - public Nucleus[] nuclei { - get { - return _nuclei; - } - set { - _nuclei = value; - } - } - - public NucleusArray(Nucleus nucleus) { - this._nuclei = new Nucleus[1]; - this._nuclei[0] = nucleus; - } - public NucleusArray(ClusterPrefab cluster) { - this._nuclei = new Nucleus[0]; - } - public NucleusArray(int size, string name) { - this._nuclei = new Nucleus[size]; - } - - - public void AddNucleus(ClusterPrefab prefab) { - if (this._nuclei.Length == 0) { - Debug.LogError("Empty perceptoid array, cannot add"); - return; - } - int newLength = this._nuclei.Length + 1; - Nucleus[] newArray = new Nucleus[newLength]; - - for (int i = 0; i < this._nuclei.Length; i++) - newArray[i] = this._nuclei[i]; - if (this._nuclei[0] is Nucleus nucleus) { - newArray[newLength - 1] = nucleus.Clone(prefab); - newArray[newLength - 1].name += $": {newLength - 1}"; - } - - this._nuclei = newArray; - } - - public void RemoveNucleus() { - int newLength = this._nuclei.Length - 1; - if (newLength == 0) { - Debug.LogWarning("Perceptoid array cannot be empty"); - return; - } - Nucleus[] newPerceptei = new Nucleus[newLength]; - for (int i = 0; i < newLength; i++) - newPerceptei[i] = this._nuclei[i]; - // Delete the last perception - if (this._nuclei[newLength] is Nucleus nucleus) - Neuron.Delete(nucleus); //this._nuclei[newLength]); - - this._nuclei = newPerceptei; - } - - public Dictionary thingReceivers = new(); - - - private Nucleus FindReceiver(int thingId, float3 inputValue) { - // No existing nucleus for this thing - float inputMagnitude = length(inputValue); - Neuron selectedReceiver = null; - float selectedMagnitude = 0; - foreach (Nucleus nucleusReceiver in this._nuclei) { - if (nucleusReceiver is not Neuron receiver) - continue; - if (thingReceivers.ContainsValue(receiver) == false) { - // We found an unusued receiver - thingReceivers.Add(thingId, receiver); - return receiver; - } - else if (receiver.isSleeping) { - // A sleeping receiver is not active and can therefore always be used - thingReceivers.Add(thingId, receiver); - return receiver; - } - else if (selectedReceiver == null) { - // If we haven't found a receiver yet, just start by taking the first - selectedReceiver = receiver; - selectedMagnitude = length(selectedReceiver.outputValue); - } - // Look for the receiver with the lowest magnitude - else { - float magnitude = length(receiver.outputValue); - - if (magnitude < inputMagnitude && length(receiver.outputValue) < selectedMagnitude) { - selectedReceiver = receiver; - selectedMagnitude = length(selectedReceiver.outputValue); - } - } - } - if (selectedReceiver != null) { - // Replace the receiver - // Find the thingId current associated with the receiver - int keyToRemove = thingReceivers.FirstOrDefault(r => r.Value.Equals(selectedReceiver)).Key; - if (keyToRemove != 0 || thingReceivers.ContainsKey(keyToRemove)) - thingReceivers.Remove(keyToRemove); - // And add the new association - thingReceivers.Add(thingId, selectedReceiver); - } - return selectedReceiver; - } - - public virtual void ProcessStimulus(int thingId, Vector3 inputValue, string thingName = null) { - CleanupReceivers(); - - if (this._nuclei[0] is Neuron neuron) - inputValue = neuron.Activator(inputValue); - - if (!thingReceivers.TryGetValue(thingId, out Nucleus selectedReceiver)) { - // No existing nucleus for this thing - selectedReceiver = FindReceiver(thingId, inputValue); - } - if (selectedReceiver == null) - return; - - if (thingName != null) { - string baseName = selectedReceiver.name; - int colonPos = selectedReceiver.name.IndexOf(":"); - if (colonPos > 0) - baseName = selectedReceiver.name[..colonPos]; - selectedReceiver.name = baseName + ": " + thingName; - } - - if (selectedReceiver is Neuron selectedNucleus) - selectedNucleus.ProcessStimulusDirect(inputValue); - } - - private void CleanupReceivers() { - // Remove a thing-receiver connection when the nucleus is inactive - List receiversToRemove = new(); - foreach (KeyValuePair item in thingReceivers) { - if (item.Value != null && item.Value is Neuron neuron && neuron.isSleeping) - receiversToRemove.Add(item.Key); - } - foreach (int thingId in receiversToRemove) { - Nucleus selectedReceiver = thingReceivers[thingId]; - - thingReceivers.Remove(thingId); - - int colonPos = selectedReceiver.name.IndexOf(":"); - if (colonPos > 0) - selectedReceiver.name = selectedReceiver.name[..colonPos]; - - } - } -} \ No newline at end of file diff --git a/NanoBrain/NucleusArray.cs.meta b/NanoBrain/NucleusArray.cs.meta deleted file mode 100644 index b03ebae..0000000 --- a/NanoBrain/NucleusArray.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f8cac60bd79854595a8571c042f77998 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Receptor.cs b/NanoBrain/Receptor.cs deleted file mode 100644 index a102835..0000000 --- a/NanoBrain/Receptor.cs +++ /dev/null @@ -1,78 +0,0 @@ -using UnityEngine; -using Unity.Mathematics; -using static Unity.Mathematics.math; - -[System.Serializable] -public class Receptor : Neuron, IReceptor { - public Receptor(Cluster parent, string name) : base(parent, name) { - this.array = new NucleusArray(this); - if (this.name.IndexOf(":") < 0) - this.name += ": 0"; - } - public Receptor(ClusterPrefab prefab, string name) : base(prefab, name) { - this.array = new NucleusArray(this); - } - - public string GetName() { - return this.name; - } - - public override Nucleus ShallowCloneTo(Cluster parent) { - Receptor clone = new(parent, name) { - - }; - CloneFields(clone); - return clone; - } - public override Nucleus Clone(ClusterPrefab prefab) { - Receptor clone = new(prefab, name) { - array = this._array - }; - CloneFields(clone); - // Adding receivers will also add synapses to the receivers - foreach (Nucleus receiver in this.receivers.ToArray()) - clone.AddReceiver(receiver); - - return clone; - } - - [SerializeReference] - private NucleusArray _array; - public NucleusArray array { - set { _array = value; } - } - - public Nucleus[] nucleiArray { - get { return _array.nuclei; } - set { _array.nuclei = value; } - } - - public void AddReceptorElement(ClusterPrefab prefab) { - IReceptorHelpers.AddReceptorElement(this, prefab); - } - - public void RemoveReceptorElement() { - IReceptorHelpers.RemoveReceptorElement(this); - } - - public virtual void AddArrayReceiver(Nucleus receiverToAdd, float weight = 1) { - IReceptorHelpers.AddArrayReceiver(this, receiverToAdd, weight); - } - - public override void UpdateStateIsolated() { - this.outputValue = this.bias; - } - - public override void UpdateNuclei() { - this.stale++; - if (this.stale > staleValueForSleep && lengthsq(this.bias) > 0) { - this.bias = new float3(0, 0, 0); - this.parent.UpdateFromNucleus(this); - } - } - - public override void ProcessStimulus(Vector3 inputValue, int thingId = 0, string thingName = null) { - this._array ??= new NucleusArray(this.parent); - this._array.ProcessStimulus(thingId, inputValue, thingName); - } -} \ No newline at end of file diff --git a/NanoBrain/Receptor.cs.meta b/NanoBrain/Receptor.cs.meta deleted file mode 100644 index 55f0467..0000000 --- a/NanoBrain/Receptor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: cfb9734aebc3ab85aacf87d26fb92e55 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Scene.meta b/NanoBrain/Scene.meta deleted file mode 100644 index d71b5e5..0000000 --- a/NanoBrain/Scene.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: bfd7dadd61c0891d8a94db0196e61a8a -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Scene/TestScene Boid.unity b/NanoBrain/Scene/TestScene Boid.unity deleted file mode 100644 index 401756e..0000000 --- a/NanoBrain/Scene/TestScene Boid.unity +++ /dev/null @@ -1,487 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!29 &1 -OcclusionCullingSettings: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_OcclusionBakeSettings: - smallestOccluder: 5 - smallestHole: 0.25 - backfaceThreshold: 100 - m_SceneGUID: 00000000000000000000000000000000 - m_OcclusionCullingData: {fileID: 0} ---- !u!104 &2 -RenderSettings: - m_ObjectHideFlags: 0 - serializedVersion: 10 - m_Fog: 0 - m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} - m_FogMode: 3 - m_FogDensity: 0.01 - m_LinearFogStart: 0 - m_LinearFogEnd: 300 - m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} - m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} - m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} - m_AmbientIntensity: 1 - m_AmbientMode: 0 - m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} - m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} - m_HaloStrength: 0.5 - m_FlareStrength: 1 - m_FlareFadeSpeed: 3 - m_HaloTexture: {fileID: 0} - m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} - m_DefaultReflectionMode: 0 - m_DefaultReflectionResolution: 128 - m_ReflectionBounces: 1 - m_ReflectionIntensity: 1 - m_CustomReflection: {fileID: 0} - m_Sun: {fileID: 0} - m_UseRadianceAmbientProbe: 0 ---- !u!157 &3 -LightmapSettings: - m_ObjectHideFlags: 0 - serializedVersion: 13 - m_BakeOnSceneLoad: 0 - m_GISettings: - serializedVersion: 2 - m_BounceScale: 1 - m_IndirectOutputScale: 1 - m_AlbedoBoost: 1 - m_EnvironmentLightingMode: 0 - m_EnableBakedLightmaps: 1 - m_EnableRealtimeLightmaps: 0 - m_LightmapEditorSettings: - serializedVersion: 12 - m_Resolution: 2 - m_BakeResolution: 40 - m_AtlasSize: 1024 - m_AO: 0 - m_AOMaxDistance: 1 - m_CompAOExponent: 1 - m_CompAOExponentDirect: 0 - m_ExtractAmbientOcclusion: 0 - m_Padding: 2 - m_LightmapParameters: {fileID: 0} - m_LightmapsBakeMode: 1 - m_TextureCompression: 1 - m_ReflectionCompression: 2 - m_MixedBakeMode: 2 - m_BakeBackend: 2 - m_PVRSampling: 1 - m_PVRDirectSampleCount: 32 - m_PVRSampleCount: 512 - m_PVRBounces: 2 - m_PVREnvironmentSampleCount: 256 - m_PVREnvironmentReferencePointCount: 2048 - m_PVRFilteringMode: 1 - m_PVRDenoiserTypeDirect: 1 - m_PVRDenoiserTypeIndirect: 1 - m_PVRDenoiserTypeAO: 1 - m_PVRFilterTypeDirect: 0 - m_PVRFilterTypeIndirect: 0 - m_PVRFilterTypeAO: 0 - m_PVREnvironmentMIS: 1 - m_PVRCulling: 1 - m_PVRFilteringGaussRadiusDirect: 1 - m_PVRFilteringGaussRadiusIndirect: 1 - m_PVRFilteringGaussRadiusAO: 1 - m_PVRFilteringAtrousPositionSigmaDirect: 0.5 - m_PVRFilteringAtrousPositionSigmaIndirect: 2 - m_PVRFilteringAtrousPositionSigmaAO: 1 - m_ExportTrainingData: 0 - m_TrainingDataDestination: TrainingData - m_LightProbeSampleCountMultiplier: 4 - m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} - m_LightingSettings: {fileID: 0} ---- !u!196 &4 -NavMeshSettings: - serializedVersion: 2 - m_ObjectHideFlags: 0 - m_BuildSettings: - serializedVersion: 3 - agentTypeID: 0 - agentRadius: 0.5 - agentHeight: 2 - agentSlope: 45 - agentClimb: 0.4 - ledgeDropHeight: 0 - maxJumpAcrossDistance: 0 - minRegionArea: 2 - manualCellSize: 0 - cellSize: 0.16666667 - manualTileSize: 0 - tileSize: 256 - buildHeightMesh: 0 - maxJobWorkers: 0 - preserveTilesOutsideBounds: 0 - debug: - m_Flags: 0 - m_NavMeshData: {fileID: 0} ---- !u!1001 &551770709 -PrefabInstance: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_Modification: - serializedVersion: 3 - m_TransformParent: {fileID: 0} - m_Modifications: - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalPosition.x - value: 0.71 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalPosition.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalPosition.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalRotation.w - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalRotation.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalRotation.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalRotation.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalEulerAnglesHint.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalEulerAnglesHint.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalEulerAnglesHint.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093763, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_Name - value: Boid2 - objectReference: {fileID: 0} - m_RemovedComponents: [] - m_RemovedGameObjects: [] - m_AddedGameObjects: [] - m_AddedComponents: [] - m_SourcePrefab: {fileID: 100100000, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} ---- !u!1 &968074744 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 968074747} - - component: {fileID: 968074746} - - component: {fileID: 968074745} - m_Layer: 0 - m_Name: Main Camera - m_TagString: MainCamera - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!81 &968074745 -AudioListener: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 968074744} - m_Enabled: 1 ---- !u!20 &968074746 -Camera: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 968074744} - m_Enabled: 1 - serializedVersion: 2 - m_ClearFlags: 1 - m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} - m_projectionMatrixMode: 1 - m_GateFitMode: 2 - m_FOVAxisMode: 0 - m_Iso: 200 - m_ShutterSpeed: 0.005 - m_Aperture: 16 - m_FocusDistance: 10 - m_FocalLength: 50 - m_BladeCount: 5 - m_Curvature: {x: 2, y: 11} - m_BarrelClipping: 0.25 - m_Anamorphism: 0 - m_SensorSize: {x: 36, y: 24} - m_LensShift: {x: 0, y: 0} - m_NormalizedViewPortRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 1 - height: 1 - near clip plane: 0.3 - far clip plane: 1000 - field of view: 60 - orthographic: 0 - orthographic size: 5 - m_Depth: -1 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingPath: -1 - m_TargetTexture: {fileID: 0} - m_TargetDisplay: 0 - m_TargetEye: 3 - m_HDR: 1 - m_AllowMSAA: 1 - m_AllowDynamicResolution: 0 - m_ForceIntoRT: 0 - m_OcclusionCulling: 1 - m_StereoConvergence: 10 - m_StereoSeparation: 0.022 ---- !u!4 &968074747 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 968074744} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 1, z: -10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &1342149740 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1342149742} - - component: {fileID: 1342149741} - m_Layer: 0 - m_Name: SwamControl - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &1342149741 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1342149740} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 0464906885ae3494f8fd0314719fb2db, type: 3} - m_Name: - m_EditorClassIdentifier: Assembly-CSharp::SwarmControl - speed: 0.5 - inertia: 0.1 - alignmentForce: 0 - cohesionForce: 1 - separationForce: 1 - avoidanceForce: 5 - separationDistance: 0.5 - perceptionDistance: 1 - spaceSize: {x: 10, y: 10, z: 10} - boundaryWidth: {x: 1, y: 1, z: 1} ---- !u!4 &1342149742 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1342149740} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -1.00377, y: -1.02283, z: 0.72231} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &2011285159 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 2011285161} - - component: {fileID: 2011285160} - m_Layer: 0 - m_Name: Directional Light - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!108 &2011285160 -Light: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2011285159} - m_Enabled: 1 - serializedVersion: 12 - m_Type: 1 - m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} - m_Intensity: 1 - m_Range: 10 - m_SpotAngle: 30 - m_InnerSpotAngle: 21.80208 - m_CookieSize2D: {x: 0.5, y: 0.5} - m_Shadows: - m_Type: 2 - m_Resolution: -1 - m_CustomResolution: -1 - m_Strength: 1 - m_Bias: 0.05 - m_NormalBias: 0.4 - m_NearPlane: 0.2 - m_CullingMatrixOverride: - e00: 1 - e01: 0 - e02: 0 - e03: 0 - e10: 0 - e11: 1 - e12: 0 - e13: 0 - e20: 0 - e21: 0 - e22: 1 - e23: 0 - e30: 0 - e31: 0 - e32: 0 - e33: 1 - m_UseCullingMatrixOverride: 0 - m_Cookie: {fileID: 0} - m_DrawHalo: 0 - m_Flare: {fileID: 0} - m_RenderMode: 0 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingLayerMask: 1 - m_Lightmapping: 4 - m_LightShadowCasterMode: 0 - m_AreaSize: {x: 1, y: 1} - m_BounceIntensity: 1 - m_ColorTemperature: 6570 - m_UseColorTemperature: 0 - m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} - m_UseBoundingSphereOverride: 0 - m_UseViewFrustumForShadowCasterCull: 1 - m_ForceVisible: 0 - m_ShadowRadius: 0 - m_ShadowAngle: 0 - m_LightUnit: 1 - m_LuxAtDistance: 1 - m_EnableSpotReflector: 1 ---- !u!4 &2011285161 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2011285159} - serializedVersion: 2 - m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} - m_LocalPosition: {x: 0, y: 3, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} ---- !u!1001 &4573752827112804207 -PrefabInstance: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_Modification: - serializedVersion: 3 - m_TransformParent: {fileID: 0} - m_Modifications: - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalPosition.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalPosition.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalPosition.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalRotation.w - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalRotation.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalRotation.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalRotation.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalEulerAnglesHint.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalEulerAnglesHint.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093762, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_LocalEulerAnglesHint.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7761516481062093763, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} - propertyPath: m_Name - value: Boid1 - objectReference: {fileID: 0} - m_RemovedComponents: [] - m_RemovedGameObjects: [] - m_AddedGameObjects: [] - m_AddedComponents: [] - m_SourcePrefab: {fileID: 100100000, guid: 6860355b30724b5ddb35781dcaf3b57e, type: 3} ---- !u!1660057539 &9223372036854775807 -SceneRoots: - m_ObjectHideFlags: 0 - m_Roots: - - {fileID: 968074747} - - {fileID: 2011285161} - - {fileID: 4573752827112804207} - - {fileID: 551770709} - - {fileID: 1342149742} diff --git a/NanoBrain/Scene/TestScene Boid.unity.meta b/NanoBrain/Scene/TestScene Boid.unity.meta deleted file mode 100644 index 81fe061..0000000 --- a/NanoBrain/Scene/TestScene Boid.unity.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 4f343147e37db9eeda3e98058c553c92 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Scene/TestScene Experiment.unity b/NanoBrain/Scene/TestScene Experiment.unity deleted file mode 100644 index ac54ba4..0000000 --- a/NanoBrain/Scene/TestScene Experiment.unity +++ /dev/null @@ -1,365 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!29 &1 -OcclusionCullingSettings: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_OcclusionBakeSettings: - smallestOccluder: 5 - smallestHole: 0.25 - backfaceThreshold: 100 - m_SceneGUID: 00000000000000000000000000000000 - m_OcclusionCullingData: {fileID: 0} ---- !u!104 &2 -RenderSettings: - m_ObjectHideFlags: 0 - serializedVersion: 10 - m_Fog: 0 - m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} - m_FogMode: 3 - m_FogDensity: 0.01 - m_LinearFogStart: 0 - m_LinearFogEnd: 300 - m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} - m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} - m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} - m_AmbientIntensity: 1 - m_AmbientMode: 0 - m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} - m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} - m_HaloStrength: 0.5 - m_FlareStrength: 1 - m_FlareFadeSpeed: 3 - m_HaloTexture: {fileID: 0} - m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} - m_DefaultReflectionMode: 0 - m_DefaultReflectionResolution: 128 - m_ReflectionBounces: 1 - m_ReflectionIntensity: 1 - m_CustomReflection: {fileID: 0} - m_Sun: {fileID: 0} - m_UseRadianceAmbientProbe: 0 ---- !u!157 &3 -LightmapSettings: - m_ObjectHideFlags: 0 - serializedVersion: 13 - m_BakeOnSceneLoad: 0 - m_GISettings: - serializedVersion: 2 - m_BounceScale: 1 - m_IndirectOutputScale: 1 - m_AlbedoBoost: 1 - m_EnvironmentLightingMode: 0 - m_EnableBakedLightmaps: 1 - m_EnableRealtimeLightmaps: 0 - m_LightmapEditorSettings: - serializedVersion: 12 - m_Resolution: 2 - m_BakeResolution: 40 - m_AtlasSize: 1024 - m_AO: 0 - m_AOMaxDistance: 1 - m_CompAOExponent: 1 - m_CompAOExponentDirect: 0 - m_ExtractAmbientOcclusion: 0 - m_Padding: 2 - m_LightmapParameters: {fileID: 0} - m_LightmapsBakeMode: 1 - m_TextureCompression: 1 - m_ReflectionCompression: 2 - m_MixedBakeMode: 2 - m_BakeBackend: 2 - m_PVRSampling: 1 - m_PVRDirectSampleCount: 32 - m_PVRSampleCount: 512 - m_PVRBounces: 2 - m_PVREnvironmentSampleCount: 256 - m_PVREnvironmentReferencePointCount: 2048 - m_PVRFilteringMode: 1 - m_PVRDenoiserTypeDirect: 1 - m_PVRDenoiserTypeIndirect: 1 - m_PVRDenoiserTypeAO: 1 - m_PVRFilterTypeDirect: 0 - m_PVRFilterTypeIndirect: 0 - m_PVRFilterTypeAO: 0 - m_PVREnvironmentMIS: 1 - m_PVRCulling: 1 - m_PVRFilteringGaussRadiusDirect: 1 - m_PVRFilteringGaussRadiusIndirect: 1 - m_PVRFilteringGaussRadiusAO: 1 - m_PVRFilteringAtrousPositionSigmaDirect: 0.5 - m_PVRFilteringAtrousPositionSigmaIndirect: 2 - m_PVRFilteringAtrousPositionSigmaAO: 1 - m_ExportTrainingData: 0 - m_TrainingDataDestination: TrainingData - m_LightProbeSampleCountMultiplier: 4 - m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} - m_LightingSettings: {fileID: 0} ---- !u!196 &4 -NavMeshSettings: - serializedVersion: 2 - m_ObjectHideFlags: 0 - m_BuildSettings: - serializedVersion: 3 - agentTypeID: 0 - agentRadius: 0.5 - agentHeight: 2 - agentSlope: 45 - agentClimb: 0.4 - ledgeDropHeight: 0 - maxJumpAcrossDistance: 0 - minRegionArea: 2 - manualCellSize: 0 - cellSize: 0.16666667 - manualTileSize: 0 - tileSize: 256 - buildHeightMesh: 0 - maxJobWorkers: 0 - preserveTilesOutsideBounds: 0 - debug: - m_Flags: 0 - m_NavMeshData: {fileID: 0} ---- !u!1 &388118692 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 388118694} - - component: {fileID: 388118693} - m_Layer: 0 - m_Name: GameObject - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &388118693 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 388118692} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 9051408e82b511584998506096af4bf0, type: 3} - m_Name: - m_EditorClassIdentifier: Assembly-CSharp::SelectorBrain - defaultBrain: {fileID: 11400000, guid: d5b3a22d9bb7d13aeb3174077125967b, type: 2} - input1: {x: 0, y: 0, z: 1} - input2: {x: 0, y: -2, z: 0} - output: {x: 0, y: 0, z: 0} ---- !u!4 &388118694 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 388118692} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -2.01476, y: -0, z: 0.65362} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &968074744 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 968074747} - - component: {fileID: 968074746} - - component: {fileID: 968074745} - m_Layer: 0 - m_Name: Main Camera - m_TagString: MainCamera - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!81 &968074745 -AudioListener: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 968074744} - m_Enabled: 1 ---- !u!20 &968074746 -Camera: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 968074744} - m_Enabled: 1 - serializedVersion: 2 - m_ClearFlags: 1 - m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} - m_projectionMatrixMode: 1 - m_GateFitMode: 2 - m_FOVAxisMode: 0 - m_Iso: 200 - m_ShutterSpeed: 0.005 - m_Aperture: 16 - m_FocusDistance: 10 - m_FocalLength: 50 - m_BladeCount: 5 - m_Curvature: {x: 2, y: 11} - m_BarrelClipping: 0.25 - m_Anamorphism: 0 - m_SensorSize: {x: 36, y: 24} - m_LensShift: {x: 0, y: 0} - m_NormalizedViewPortRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 1 - height: 1 - near clip plane: 0.3 - far clip plane: 1000 - field of view: 60 - orthographic: 0 - orthographic size: 5 - m_Depth: -1 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingPath: -1 - m_TargetTexture: {fileID: 0} - m_TargetDisplay: 0 - m_TargetEye: 3 - m_HDR: 1 - m_AllowMSAA: 1 - m_AllowDynamicResolution: 0 - m_ForceIntoRT: 0 - m_OcclusionCulling: 1 - m_StereoConvergence: 10 - m_StereoSeparation: 0.022 ---- !u!4 &968074747 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 968074744} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 1, z: -10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &2011285159 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 2011285161} - - component: {fileID: 2011285160} - m_Layer: 0 - m_Name: Directional Light - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!108 &2011285160 -Light: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2011285159} - m_Enabled: 1 - serializedVersion: 12 - m_Type: 1 - m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} - m_Intensity: 1 - m_Range: 10 - m_SpotAngle: 30 - m_InnerSpotAngle: 21.80208 - m_CookieSize2D: {x: 0.5, y: 0.5} - m_Shadows: - m_Type: 2 - m_Resolution: -1 - m_CustomResolution: -1 - m_Strength: 1 - m_Bias: 0.05 - m_NormalBias: 0.4 - m_NearPlane: 0.2 - m_CullingMatrixOverride: - e00: 1 - e01: 0 - e02: 0 - e03: 0 - e10: 0 - e11: 1 - e12: 0 - e13: 0 - e20: 0 - e21: 0 - e22: 1 - e23: 0 - e30: 0 - e31: 0 - e32: 0 - e33: 1 - m_UseCullingMatrixOverride: 0 - m_Cookie: {fileID: 0} - m_DrawHalo: 0 - m_Flare: {fileID: 0} - m_RenderMode: 0 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingLayerMask: 1 - m_Lightmapping: 4 - m_LightShadowCasterMode: 0 - m_AreaSize: {x: 1, y: 1} - m_BounceIntensity: 1 - m_ColorTemperature: 6570 - m_UseColorTemperature: 0 - m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} - m_UseBoundingSphereOverride: 0 - m_UseViewFrustumForShadowCasterCull: 1 - m_ForceVisible: 0 - m_ShadowRadius: 0 - m_ShadowAngle: 0 - m_LightUnit: 1 - m_LuxAtDistance: 1 - m_EnableSpotReflector: 1 ---- !u!4 &2011285161 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2011285159} - serializedVersion: 2 - m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} - m_LocalPosition: {x: 0, y: 3, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} ---- !u!1660057539 &9223372036854775807 -SceneRoots: - m_ObjectHideFlags: 0 - m_Roots: - - {fileID: 968074747} - - {fileID: 2011285161} - - {fileID: 388118694} diff --git a/NanoBrain/Scene/TestScene Experiment.unity.meta b/NanoBrain/Scene/TestScene Experiment.unity.meta deleted file mode 100644 index 676153c..0000000 --- a/NanoBrain/Scene/TestScene Experiment.unity.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 1070383882ed0f5379a3b34e8ccb1f75 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Scripts.meta b/NanoBrain/Scripts.meta deleted file mode 100644 index 6083b0e..0000000 --- a/NanoBrain/Scripts.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 363b69b84de0e4b729794c10e7c40ab5 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Scripts/Experimental.meta b/NanoBrain/Scripts/Experimental.meta deleted file mode 100644 index 7c7ad14..0000000 --- a/NanoBrain/Scripts/Experimental.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 2c1e3956a0b70ae6b8d09fb467b73621 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Scripts/NeuraalNetwerkIcoonSchets1.png b/NanoBrain/Scripts/NeuraalNetwerkIcoonSchets1.png deleted file mode 100644 index 82980ef9531ebefbf60dfd4bb2d8f0c0b99f2798..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63771 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 s)rO#lD@ diff --git a/NanoBrain/Scripts/NeuraalNetwerkIcoonSchets1.png.meta b/NanoBrain/Scripts/NeuraalNetwerkIcoonSchets1.png.meta deleted file mode 100644 index f31713f..0000000 --- a/NanoBrain/Scripts/NeuraalNetwerkIcoonSchets1.png.meta +++ /dev/null @@ -1,117 +0,0 @@ -fileFormatVersion: 2 -guid: cad48149d984d2eddae5808eb1517cb5 -TextureImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 13 - mipmaps: - mipMapMode: 0 - enableMipMap: 1 - sRGBTexture: 1 - linearTexture: 0 - fadeOut: 0 - borderMipMap: 0 - mipMapsPreserveCoverage: 0 - alphaTestReferenceValue: 0.5 - mipMapFadeDistanceStart: 1 - mipMapFadeDistanceEnd: 3 - bumpmap: - convertToNormalMap: 0 - externalNormalMap: 0 - heightScale: 0.25 - normalMapFilter: 0 - flipGreenChannel: 0 - isReadable: 0 - streamingMipmaps: 0 - streamingMipmapsPriority: 0 - vTOnly: 0 - ignoreMipmapLimit: 0 - grayScaleToAlpha: 0 - generateCubemap: 6 - cubemapConvolution: 0 - seamlessCubemap: 0 - textureFormat: 1 - maxTextureSize: 2048 - textureSettings: - serializedVersion: 2 - filterMode: 1 - aniso: 1 - mipBias: 0 - wrapU: 0 - wrapV: 0 - wrapW: 0 - nPOTScale: 1 - lightmap: 0 - compressionQuality: 50 - spriteMode: 0 - spriteExtrude: 1 - spriteMeshType: 1 - alignment: 0 - spritePivot: {x: 0.5, y: 0.5} - spritePixelsToUnits: 100 - spriteBorder: {x: 0, y: 0, z: 0, w: 0} - spriteGenerateFallbackPhysicsShape: 1 - alphaUsage: 1 - alphaIsTransparency: 0 - spriteTessellationDetail: -1 - textureType: 0 - textureShape: 1 - singleChannelComponent: 0 - flipbookRows: 1 - flipbookColumns: 1 - maxTextureSizeSet: 0 - compressionQualitySet: 0 - textureFormatSet: 0 - ignorePngGamma: 0 - applyGammaDecoding: 0 - swizzle: 50462976 - cookieLightType: 0 - platformSettings: - - serializedVersion: 4 - buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 4 - buildTarget: Standalone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - spriteSheet: - serializedVersion: 2 - sprites: [] - outline: [] - customData: - physicsShape: [] - bones: [] - spriteID: - internalID: 0 - vertices: [] - indices: - edges: [] - weights: [] - secondaryTextures: [] - spriteCustomMetadata: - entries: [] - nameFileIdTable: {} - mipmapLimitGroupName: - pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Scripts/NeuraalNetwerkIcoonSchets2.png b/NanoBrain/Scripts/NeuraalNetwerkIcoonSchets2.png deleted file mode 100644 index 35853d65078c2e6c2326da8a10e5f8d57db43a76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39373 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 diff --git a/NanoBrain/Scripts/NeuraalNetwerkIcoonSchets2.png.meta b/NanoBrain/Scripts/NeuraalNetwerkIcoonSchets2.png.meta deleted file mode 100644 index 9abd599..0000000 --- a/NanoBrain/Scripts/NeuraalNetwerkIcoonSchets2.png.meta +++ /dev/null @@ -1,117 +0,0 @@ -fileFormatVersion: 2 -guid: 2e644ed036e8939bf94586314a4f4607 -TextureImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 13 - mipmaps: - mipMapMode: 0 - enableMipMap: 1 - sRGBTexture: 1 - linearTexture: 0 - fadeOut: 0 - borderMipMap: 0 - mipMapsPreserveCoverage: 0 - alphaTestReferenceValue: 0.5 - mipMapFadeDistanceStart: 1 - mipMapFadeDistanceEnd: 3 - bumpmap: - convertToNormalMap: 0 - externalNormalMap: 0 - heightScale: 0.25 - normalMapFilter: 0 - flipGreenChannel: 0 - isReadable: 0 - streamingMipmaps: 0 - streamingMipmapsPriority: 0 - vTOnly: 0 - ignoreMipmapLimit: 0 - grayScaleToAlpha: 0 - generateCubemap: 6 - cubemapConvolution: 0 - seamlessCubemap: 0 - textureFormat: 1 - maxTextureSize: 2048 - textureSettings: - serializedVersion: 2 - filterMode: 1 - aniso: 1 - mipBias: 0 - wrapU: 0 - wrapV: 0 - wrapW: 0 - nPOTScale: 1 - lightmap: 0 - compressionQuality: 50 - spriteMode: 0 - spriteExtrude: 1 - spriteMeshType: 1 - alignment: 0 - spritePivot: {x: 0.5, y: 0.5} - spritePixelsToUnits: 100 - spriteBorder: {x: 0, y: 0, z: 0, w: 0} - spriteGenerateFallbackPhysicsShape: 1 - alphaUsage: 1 - alphaIsTransparency: 0 - spriteTessellationDetail: -1 - textureType: 0 - textureShape: 1 - singleChannelComponent: 0 - flipbookRows: 1 - flipbookColumns: 1 - maxTextureSizeSet: 0 - compressionQualitySet: 0 - textureFormatSet: 0 - ignorePngGamma: 0 - applyGammaDecoding: 0 - swizzle: 50462976 - cookieLightType: 0 - platformSettings: - - serializedVersion: 4 - buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 4 - buildTarget: Standalone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - spriteSheet: - serializedVersion: 2 - sprites: [] - outline: [] - customData: - physicsShape: [] - bones: [] - spriteID: - internalID: 0 - vertices: [] - indices: - edges: [] - weights: [] - secondaryTextures: [] - spriteCustomMetadata: - entries: [] - nameFileIdTable: {} - mipmapLimitGroupName: - pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Synapse.cs b/NanoBrain/Synapse.cs deleted file mode 100644 index 424b7e6..0000000 --- a/NanoBrain/Synapse.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using UnityEngine; - -[Serializable] -public class Synapse { - [SerializeReference] - public Neuron neuron; - - public float weight; - - public Synapse(Neuron nucleus, float weight = 1.0f) { - this.neuron = nucleus; - this.weight = weight; - } -} \ No newline at end of file diff --git a/NanoBrain/Synapse.cs.meta b/NanoBrain/Synapse.cs.meta deleted file mode 100644 index 7c45e30..0000000 --- a/NanoBrain/Synapse.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 334a58eafccd60cbdb32f719e9e861c6 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/NanoBrain/Velocity.asset b/NanoBrain/Velocity.asset deleted file mode 100644 index 0001385..0000000 --- a/NanoBrain/Velocity.asset +++ /dev/null @@ -1,128 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 60a957541c24c57e78018c202ebb1d9b, type: 3} - m_Name: Velocity - m_EditorClassIdentifier: Assembly-CSharp::ClusterPrefab - nuclei: - - rid: 2262690551513219315 - - rid: 2262690551513219316 - - rid: 2262690551513219317 - references: - version: 2 - RefIds: - - rid: -2 - type: {class: , ns: , asm: } - - rid: 2262690551513219315 - type: {class: Neuron, ns: , asm: Assembly-CSharp} - data: - name: Velocity - clusterPrefab: {fileID: 11400000} - parent: - rid: -2 - trace: 0 - bias: {x: 0, y: 0, z: 0} - _synapses: - - nucleus: - rid: 2262690551513219316 - weight: 1 - - nucleus: - rid: 2262690551513219317 - weight: 1 - combinator: 0 - _curvePreset: 0 - curve: - serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0 - value: 0 - inSlope: 0 - outSlope: 1 - tangentMode: 0 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 1000 - value: 1000 - inSlope: 1 - outSlope: 0 - tangentMode: 0 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 - curveMax: 1 - _receivers: [] - - rid: 2262690551513219316 - type: {class: Neuron, ns: , asm: Assembly-CSharp} - data: - name: Position - clusterPrefab: {fileID: 11400000} - parent: - rid: -2 - trace: 0 - bias: {x: 0, y: 0, z: 0} - _synapses: [] - combinator: 0 - _curvePreset: 0 - curve: - serializedVersion: 2 - m_Curve: - - serializedVersion: 3 - time: 0 - value: 0 - inSlope: 0 - outSlope: 1 - tangentMode: 0 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - - serializedVersion: 3 - time: 1000 - value: 1000 - inSlope: 1 - outSlope: 0 - tangentMode: 0 - weightedMode: 0 - inWeight: 0 - outWeight: 0 - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 - curveMax: 1 - _receivers: - - rid: 2262690551513219315 - - rid: 2262690551513219317 - type: {class: MemoryCell, ns: , asm: Assembly-CSharp} - data: - name: New memory cell - clusterPrefab: {fileID: 11400000} - parent: - rid: -2 - trace: 0 - bias: {x: 0, y: 0, z: 0} - _synapses: [] - combinator: 0 - _curvePreset: 0 - curve: - serializedVersion: 2 - m_Curve: [] - m_PreInfinity: 2 - m_PostInfinity: 2 - m_RotationOrder: 4 - curveMax: 1 - _receivers: - - rid: 2262690551513219315 - staticMemory: 0 diff --git a/NanoBrain/Velocity.asset.meta b/NanoBrain/Velocity.asset.meta deleted file mode 100644 index 38684df..0000000 --- a/NanoBrain/Velocity.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: c61aecac62c26de4aaefb2612bcc9a5d -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Samples/Animation/AntAnimator.controller b/Samples/Animation/AntAnimator.controller index c14185c..7f03712 100644 --- a/Samples/Animation/AntAnimator.controller +++ b/Samples/Animation/AntAnimator.controller @@ -25,7 +25,7 @@ BlendTree: m_DirectBlendParameter: Forward m_Mirror: 0 - serializedVersion: 2 - m_Motion: {fileID: 7400000, guid: ab82ff68e62ea3b1c8e6523f8d46c142, type: 2} + m_Motion: {fileID: 0} m_Threshold: 0.6666667 m_Position: {x: -1, y: 0} m_TimeScale: 1 @@ -33,7 +33,7 @@ BlendTree: m_DirectBlendParameter: Forward m_Mirror: 0 - serializedVersion: 2 - m_Motion: {fileID: 7400000, guid: 91229db5e929c379bbfd5bf417848488, type: 2} + m_Motion: {fileID: 0} m_Threshold: 1 m_Position: {x: 1, y: 0} m_TimeScale: 1 diff --git a/Samples/Animation/AntWalk.anim b/Samples/Animation/AntWalk.anim index dc3e5e6..93ed7a6 100644 --- a/Samples/Animation/AntWalk.anim +++ b/Samples/Animation/AntWalk.anim @@ -117,18 +117,18 @@ AnimationClip: outWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} - serializedVersion: 3 time: 0.20833333 - value: {x: 0, y: 0, z: -0.001} - inSlope: {x: 0, y: 0, z: -0.0048} - outSlope: {x: 0, y: 0, z: -0.0048} + value: {x: 0, y: 0.00027777773, z: -0.001} + inSlope: {x: 0, y: 0.0023999996, z: -0.0048} + outSlope: {x: 0, y: 0.0023999996, z: -0.0048} tangentMode: 0 weightedMode: 0 inWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} outWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} - serializedVersion: 3 time: 0.41666666 - value: {x: 0, y: 0, z: -0.002} - inSlope: {x: 0, y: 0, z: 0} - outSlope: {x: 0, y: 0, z: 0} + value: {x: 0, y: 0.0008888887, z: -0.002} + inSlope: {x: 0, y: 0.0032, z: 0} + outSlope: {x: 0, y: 0.0032, z: 0} tangentMode: 0 weightedMode: 0 inWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} @@ -136,8 +136,8 @@ AnimationClip: - serializedVersion: 3 time: 0.625 value: {x: 0, y: 0.0015, z: -0.001} - inSlope: {x: 0, y: 0.0048, z: 0.0048} - outSlope: {x: 0, y: 0.0048, z: 0.0048} + inSlope: {x: 0, y: 0.0024, z: 0.0048} + outSlope: {x: 0, y: 0.0024, z: 0.0048} tangentMode: 0 weightedMode: 0 inWeight: {x: 0.33333334, y: 0.33333334, z: 0.33333334} @@ -328,7 +328,7 @@ AnimationClip: m_CycleOffset: 0 m_HasAdditiveReferencePose: 0 m_LoopTime: 1 - m_LoopBlend: 1 + m_LoopBlend: 0 m_LoopBlendOrientation: 0 m_LoopBlendPositionY: 0 m_LoopBlendPositionXZ: 0 @@ -558,29 +558,11 @@ AnimationClip: weightedMode: 0 inWeight: 0.33333334 outWeight: 0.33333334 - - serializedVersion: 3 - time: 0.20833333 - value: 0 - inSlope: 0 - outSlope: 0 - tangentMode: 136 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0.33333334 - - serializedVersion: 3 - time: 0.41666666 - value: 0 - inSlope: 0 - outSlope: 0 - tangentMode: 136 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0.33333334 - serializedVersion: 3 time: 0.625 value: 0.0015 - inSlope: 0.0048 - outSlope: 0.0048 + inSlope: 0.0024 + outSlope: 0.0024 tangentMode: 136 weightedMode: 0 inWeight: 0.33333334 @@ -612,24 +594,6 @@ AnimationClip: weightedMode: 0 inWeight: 0.33333334 outWeight: 0.33333334 - - serializedVersion: 3 - time: 1.4583334 - value: 0 - inSlope: 0 - outSlope: 0 - tangentMode: 136 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0.33333334 - - serializedVersion: 3 - time: 1.6666666 - value: 0 - inSlope: 0 - outSlope: 0 - tangentMode: 136 - weightedMode: 0 - inWeight: 0.33333334 - outWeight: 0.33333334 m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4