Improved network

This commit is contained in:
Pascal Serrarens 2026-01-08 17:41:43 +01:00
parent 600ecd5406
commit 5206469764
17 changed files with 7248 additions and 478 deletions

View File

@ -53,8 +53,8 @@
<Compile Include="Assets/NanoBrain/LinearAlgebra/test/Vector2IntTest.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/Quaternion.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/float16.cs" />
<Compile Include="Assets/NanoBrain/VisualEditor/NanoBrainObj.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/test/DirectionTest.cs" />
<Compile Include="Assets/NanoBrain/VisualEditor/NanoBrain.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/test/Vector2FloatTest.cs" />
<Compile Include="Assets/Scenes/Boids/Scripts/SwarmSpawner.cs" />
<Compile Include="Assets/NanoBrain/Perceptoid.cs" />
@ -62,6 +62,7 @@
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/Vector2Float.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/test/SwingTwistTest.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/Vector3Int.cs" />
<Compile Include="Assets/NanoBrain/PercepteiArray.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/Vector3Float.cs" />
<Compile Include="Assets/NanoBrain/Receptor.cs" />
<Compile Include="Assets/NanoBrain/LinearAlgebra/src/Matrix.cs" />

View File

@ -1,6 +1,4 @@
using System.Collections.Generic;
using UnityEngine;
using LinearAlgebra;
[System.Serializable]
public class Neuroid : Nucleus {
@ -80,11 +78,9 @@ public class Neuroid : Nucleus {
//Applying the weight factgors
foreach (Synapse synapse in this.synapses) {
Vector3 outputValue = synapse.nucleus.outputValue;
float magnitude = synapse.weight * outputValue.magnitude;
sum += magnitude * outputValue.normalized;
n++;
sum += synapse.weight * synapse.nucleus.outputValue;
if (synapse.nucleus.outputValue.sqrMagnitude != 0)
n++;
}
if (average)
sum /= n;

View File

@ -154,11 +154,11 @@ public class Nucleus {
public virtual void UpdateState() { }
public void UpdateResult(Vector3 result) {
float d = Vector3.Distance(result, this.outputValue);
if (d < 0.5f) {
//Debug.Log($"insignificant update: {d}");
return;
}
// float d = Vector3.Distance(result, this.outputValue);
// if (d < 0.5f) {
// //Debug.Log($"insignificant update: {d}");
// return;
// }
this.outputValue = result;
foreach (Receiver receiver in this.receivers)

View File

@ -0,0 +1,22 @@
public class PercepteiArray {
public ArrayPerceptoid[] perceptei;
public string name;
public PercepteiArray(NanoBrain brain, int thingType, string baseName, uint count) {
this.name = baseName;
this.perceptei = new ArrayPerceptoid[count];
for (uint i = 0; i < count; i++) {
this.perceptei[i] = new ArrayPerceptoid(brain, thingType, $"{baseName}[{i}]") {
array = this
};
}
}
}
public class ArrayPerceptoid : Perceptoid {
public PercepteiArray array;
public ArrayPerceptoid(NanoBrain brain, int thingType, string name = "sensor") : base(brain, thingType, name) {
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f8cac60bd79854595a8571c042f77998

View File

@ -6,7 +6,7 @@ public class Perceptoid : Neuroid {
// A neuroid which has no neurons as input
// But receives value from a receptor
public Receptor receptor;
//public VelocityNeuroid velocityNeuroid;
public string baseName;
#region Serialization
@ -58,21 +58,22 @@ public class Perceptoid : Neuroid {
this.nucleusType = nameof(Perceptoid);
this.name = name;
this.baseName = name;
this.thingType = thingType;
this.receptor = Receptor.GetReceptor(brain, thingType);
this.receptor.perceptei.Add(this);
}
public void Replace(int thingType, string name = "sensor") {
this.name = name;
// public void Replace(int thingType, string name = "sensor") {
// this.name = name;
this.thingType = thingType;
this.receptor.thingType = thingType;
this.receptor.localPosition = Vector3.zero;
// this.thingType = thingType;
// this.receptor.thingType = thingType;
// this.receptor.localPosition = Vector3.zero;
this.outputValue = Vector3.zero;
this.receivers = new();
}
// this.outputValue = Vector3.zero;
// this.receivers = new();
// }
public override void UpdateState() {
Vector3 result = this.receptor.localPosition;
@ -80,11 +81,11 @@ public class Perceptoid : Neuroid {
}
public static Perceptoid GetPerception(NanoBrain brain, int thingType = 0) {
foreach (Nucleus nucleus in brain.nuclei) {
if (nucleus is Perceptoid perceptoid && (thingType == 0 || perceptoid.receptor.thingType == thingType))
return perceptoid;
}
return null;
}
// public static Perceptoid GetPerception(NanoBrain brain, int thingType = 0) {
// foreach (Nucleus nucleus in brain.nuclei) {
// if (nucleus is Perceptoid perceptoid && (thingType == 0 || perceptoid.receptor.thingType == thingType))
// return perceptoid;
// }
// return null;
// }
}

View File

@ -37,7 +37,7 @@ public class Receptor {
return newReceptor;
}
public virtual void ProcessStimulus(int thingId, Vector3 newLocalPositionVector) {
public virtual void ProcessStimulus(int thingId, Vector3 newLocalPositionVector, string thingName = null) {
this.localPosition = newLocalPositionVector;
Perceptoid selectedPerceptoid = null;
@ -75,6 +75,8 @@ public class Receptor {
}
// Debug.Log($"Stimulus {thingType} {thingId} {selectedPerceptoid.name}");
selectedPerceptoid.thingId = thingId;
if (thingName != null)
selectedPerceptoid.name = selectedPerceptoid.baseName + " " + thingName;
selectedPerceptoid.UpdateState();
}
}

View File

@ -1,3 +1,4 @@
/*
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
@ -406,7 +407,7 @@ public class GraphBoardView : VisualElement {
// }
}
/*
public class NodeView : VisualElement {
Nucleus data;
GraphBoardView board;
@ -466,7 +467,7 @@ public class NodeView : VisualElement {
//dragging = false;
}
}
*/
public class GraphNodeWrapper : ScriptableObject {
// expose fields that map to GraphNode
@ -507,3 +508,4 @@ public static class OpenAssetHandler {
return false; // let Unity open normally
}
}
*/

View File

@ -181,22 +181,6 @@ public class NanoBrainInspector : Editor {
}
// basic pan/zoom handling
// void OnWheel(WheelEvent e) {
// if (e.ctrlKey) {
// float delta = -e.delta.y * 0.001f;
// zoom = Mathf.Clamp(zoom + delta, 0.25f, 2f);
// content.transform.rotation = Quaternion.identity; // keep transform accessible
// content.transform.scale = new Vector3(zoom, zoom, 1);
// e.StopPropagation();
// }
// else {
// pan += e.delta;
// content.style.left = pan.x;
// content.style.top = pan.y;
// }
// }
void OnMouseDown(MouseDownEvent e) {
if (e.button == 2) { draggingCanvas = true; lastMouse = e.mousePosition; e.StopPropagation(); }
}
@ -220,8 +204,6 @@ public class NanoBrainInspector : Editor {
Handles.BeginGUI();
DrawGraph();
// foreach (NeuroidLayer layer in layers)
// DrawLayer(layer);
Handles.EndGUI();
}
@ -291,15 +273,24 @@ public class NanoBrainInspector : Editor {
float margin = 10 + spacing / 2;
int row = 0;
foreach (Synapse receiver in nucleus.synapses) {
Nucleus receiverNucleus = receiver.nucleus;
List<PercepteiArray> drawnArrays = new();
foreach (Synapse synapse in nucleus.synapses) {
Vector3 pos = new(250, margin + row * spacing, 0.0f);
Handles.color = Color.white;
Handles.DrawLine(parentPos, pos);
if (synapse.nucleus is ArrayPerceptoid perceptoid) {
if (drawnArrays.Contains(perceptoid.array))
// We already drawn this array
continue;
DrawNucleus(receiverNucleus, pos, maxValue, size);
row++;
drawnArrays.Add(perceptoid.array);
DrawArray(perceptoid.array, pos, size);
}
else {
DrawNucleus(synapse.nucleus, pos, maxValue, size);
row++;
}
}
}
@ -312,7 +303,7 @@ public class NanoBrainInspector : Editor {
}
Handles.DrawSolidDisc(position, Vector3.forward, size);
Vector3 labelPos = position - Vector3.down * (size + 0.2f); // below disc along up axis
GUIStyle style = new GUIStyle(EditorStyles.label) {
GUIStyle style = new(EditorStyles.label) {
alignment = TextAnchor.UpperCenter,
normal = { textColor = Color.white },
fontStyle = FontStyle.Bold
@ -335,15 +326,28 @@ public class NanoBrainInspector : Editor {
}
}
private void DrawArray(PercepteiArray array, Vector3 position, float size) {
Vector3 offset = new(size/4, size/4, 0);
Handles.color = Color.darkGray;
Handles.DrawSolidDisc(position + offset * 2, Vector3.forward, size);
Handles.color = Color.lightGray;
Handles.DrawSolidDisc(position + offset, Vector3.forward, size);
Handles.color = Color.white;
Handles.DrawSolidDisc(position, Vector3.forward, size);
Vector3 labelPos = position - Vector3.down * (size + 0.2f); // below disc along up axis
GUIStyle style = new GUIStyle(EditorStyles.label) {
alignment = TextAnchor.UpperCenter,
normal = { textColor = Color.white },
fontStyle = FontStyle.Bold
};
Handles.Label(labelPos, array.name, style);
// To do: add HandleClick (see above) to expand the array
}
private void HandleMouseHover(Nucleus nucleus, Rect rect) {
GUIContent tooltip;
// if (nucleus is SensoryNeuroid sensoryNeuroid) {
// tooltip = new(
// $"{sensoryNeuroid.name}" +
// $"\nThing {sensoryNeuroid.receptor.thingType}" +
// $"\nValue: {nucleus.outputValue}");
// }
//else
if (nucleus is Perceptoid perceptoid) {
if (perceptoid.receptor != null) {
tooltip = new(
@ -419,42 +423,43 @@ public class NanoBrainInspector : Editor {
EditorGUILayout.LabelField(" ");
if (this.currentNucleus.synapses.Count > 0) {
foreach (Synapse synapse in this.currentNucleus.synapses) {
Synapse[] synapses = this.currentNucleus.synapses.ToArray();
foreach (Synapse synapse in synapses) {
if (synapse.nucleus != null) {
EditorGUILayout.Space();
EditorGUI.BeginDisabledGroup(synapse.nucleus.isSleeping);
if (Application.isPlaying)
EditorGUILayout.FloatField(synapse.nucleus.name, synapse.nucleus.outputValue.magnitude * synapse.weight);
else
else {
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(synapse.nucleus.name);
if (GUILayout.Button("Disconnect"))
synapse.nucleus.RemoveReceiver(this.currentNucleus);
EditorGUILayout.EndHorizontal();
}
EditorGUI.indentLevel++;
// EditorGUI.BeginChangeCheck();
synapse.weight = EditorGUILayout.FloatField("Weight", synapse.weight);
// synapse.curvePreset = (Synapse.CurvePresets)EditorGUILayout.EnumPopup("Preset", synapse.curvePreset);
// if (EditorGUI.EndChangeCheck()) {
// synapse.curve = synapse.GenerateCurve();
// }
// if (synapse.curveMax > 0)
// EditorGUILayout.CurveField("Curve", synapse.curve, Color.cyan, new Rect(0, 0, 1, synapse.curveMax));
// else
// EditorGUILayout.CurveField("Curve", synapse.curve, Color.cyan, new Rect(0, synapse.curveMax, 1, -synapse.curveMax));
EditorGUI.indentLevel--;
EditorGUI.EndDisabledGroup();
}
}
//EditorGUI.indentLevel--;
}
if (GUILayout.Button("Add Neuron"))
EditorGUILayout.Space();
ConnectNucleus(this.currentNucleus);
if (GUILayout.Button("Add Input Neuron"))
AddInputNeuron(this.currentNucleus);
if (GUILayout.Button("Add Perceptoid"))
if (GUILayout.Button("Add Input Perceptoid"))
AddPerceptoid(this.currentNucleus);
EditorGUILayout.Space();
if (GUILayout.Button("Delete this neuron"))
DeleteNeuron(this.currentNucleus);
ConnectNucleus(this.currentNucleus);
DisconnectNucleus(this.currentNucleus);
//DisconnectNucleus(this.currentNucleus);
if (this.gameObject != null) {
Vector3 worldVector = this.gameObject.transform.TransformVector(this.currentNucleus.outputValue);
@ -498,9 +503,9 @@ public class NanoBrainInspector : Editor {
if (this.currentNucleus.brain == null)
return;
//string[] names = this.currentNucleus.brain.perceptei.Select(i => i.name).ToArray();
IEnumerable<string> perceptei = this.currentNucleus.brain.perceptei.Select(i => i.name);
IEnumerable<string> nuclei = this.currentNucleus.brain.nuclei.Select(i => i.name);
IEnumerable<string> synapseNuclei = this.currentNucleus.synapses.Select(synapse => synapse.nucleus.name);
IEnumerable<string> perceptei = this.currentNucleus.brain.perceptei.Select(i => i.name).Except(synapseNuclei);
IEnumerable<string> nuclei = this.currentNucleus.brain.nuclei.Select(i => i.name).Except(synapseNuclei);
string[] names = perceptei.Concat(nuclei).ToArray();
int selectedIndex = -1;
selectedIndex = EditorGUILayout.Popup("Connect to", selectedIndex, names);
@ -527,32 +532,6 @@ public class NanoBrainInspector : Editor {
synapse.nucleus.RemoveReceiver(this.currentNucleus);
}
}
// private Vector3 NodePosition(Nucleus nucleus, int layerNodeCount = 1) {
// if (this.neuroidPositions.ContainsKey(nucleus)) {
// Vector2Int nucleusPos = this.neuroidPositions[nucleus];
// return NodePosition(nucleusPos, layerNodeCount);
// }
// else {
// return Vector3.zero;
// }
// }
// private Vector3 NodePosition(Vector2Int location, int layerNodeCount = 1) {
// float spacing = 400f / layerNodeCount;
// float margin = 10 + spacing / 2;
// float size = 20;
// Vector3 parentPos = new(100 + location.x * 100 - size, margin + location.y * spacing - size, 0.1f);
// return parentPos;
// }
// public void CreateEdge(string fromId, string toId) {
// if (fromId == toId) return;
// Undo.RecordObject(graph, "Create Edge");
// graph.edges.Add(new GraphEdge { fromNodeId = fromId, toNodeId = toId });
// EditorUtility.SetDirty(graph);
// Rebuild();
// }
}
#endregion Start
@ -573,29 +552,31 @@ public class NanoBrainInspector : Editor {
}
}
// protected virtual void OnSceneGUI() {
// NanoBrain brain = target as NanoBrain;
// if (brain == null)
// return;
// Vector3 position = brain.transform.position;
// float radius = 1;
// Handles.DrawWireDisc(position, Vector3.up, radius); // horizontal circle
// Handles.DrawWireDisc(position, Vector3.right, radius); // X-plane
// Handles.DrawWireDisc(position, Vector3.forward, radius); // Z-plane
// // Debug.DrawRay(brain.transform.position, Vector3.forward, Color.magenta);
// // Handles.color = Color.green;
// // Handles.DrawLine(brain.transform.position, brain.transform.position + Vector3.up);
// // Handles.color = Color.yellow;
// // Vector3 worldForce = brain.transform.TransformDirection(this.currentNucleus.outputValue);
// // //Debug.DrawRay(position, worldForce * 10, Color.yellow);
// // Handles.DrawLine(position, position + worldForce * 10);
// }
#endregion Update
}
public class GraphNodeWrapper : ScriptableObject {
// expose fields that map to GraphNode
public string title;
public Vector2 position;
Nucleus node;
NanoBrain graph; // needed to write back and mark dirty
public GraphNodeWrapper Init(Nucleus node, NanoBrain graphAsset) {
this.node = node;
this.graph = graphAsset;
this.title = " A " + node.name;
//position = node.position;
return this;
}
void OnValidate() {
if (node != null) {
node.name = title;
//node.position = position;
#if UNITY_EDITOR
if (graph != null)
UnityEditor.EditorUtility.SetDirty(graph);
#endif
}
}
}

View File

@ -1,6 +1,5 @@
using System.Collections.Generic;
using UnityEngine;
using LinearAlgebra;
[CreateAssetMenu(menuName = "Passer/NanoBrain")]
public class NanoBrain : ScriptableObject, ISerializationCallbackReceiver {
@ -19,11 +18,8 @@ public class NanoBrain : ScriptableObject, ISerializationCallbackReceiver {
public Nucleus root;
public int rootId;
// public Perception perception;
public NanoBrain() {
this.root = new Neuroid(this, "Root");
// this.perception = new Perception(this);
}
public Neuroid AddNeuron(string name) {
@ -32,18 +28,10 @@ public class NanoBrain : ScriptableObject, ISerializationCallbackReceiver {
}
public void UpdateNuclei() {
foreach (Nucleus nucleus in nuclei) {
//nucleus.stale++;
nucleus.IncreaseAge();
// if (nucleus.isSleeping)
// nucleus.outputValue = Spherical.zero;
}
foreach (Perceptoid perception in perceptei) {
//perception.stale++;
perception.IncreaseAge();
// if (perception.isSleeping)
// perception.outputValue = Spherical.zero;
}
foreach (Nucleus nucleus in nuclei)
nucleus.IncreaseAge();
foreach (Perceptoid perception in perceptei)
perception.IncreaseAge();
}
public void OnBeforeSerialize() {
@ -57,14 +45,8 @@ public class NanoBrain : ScriptableObject, ISerializationCallbackReceiver {
nucleus.Rebuild(this);
}
// List<Nucleus> rebuildNuclei = new();
// foreach (Nucleus nucleus in this.nuclei.ToArray()) {
// rebuildNuclei.Add(Nucleus.RebuildType(this, nucleus));
// }
// this.nuclei = rebuildNuclei;
foreach (Perceptoid perceptoid in this.perceptei.ToArray()) {
perceptoid.Rebuild(this);
}
foreach (Perceptoid perceptoid in this.perceptei.ToArray())
perceptoid.Rebuild(this);
}
catch (System.Exception) { }
this.GarbageCollection();
@ -85,9 +67,6 @@ public class NanoBrain : ScriptableObject, ISerializationCallbackReceiver {
if (nucleus.brain == null)
nucleus.brain = this;
if (nucleus.name == "Boid1")
Debug.Log(" Found boiid1");
visitedNuclei.Add(nucleus);
if (nucleus.synapses != null) {
HashSet<Synapse> visitedSynapses = new();
@ -105,7 +84,6 @@ public class NanoBrain : ScriptableObject, ISerializationCallbackReceiver {
if (receiver != null && receiver.nucleus != null) {
visitedReceivers.Add(receiver);
visitedNuclei.Add(receiver.nucleus);
//MarkNuclei(visitedNuclei, receiver.nucleus);
}
}
nucleus.receivers.RemoveAll(receiver => visitedReceivers.Contains(receiver) == false);

View File

@ -4,18 +4,30 @@ public class NanoBrainComponent : MonoBehaviour {
public NanoBrain defaultBrain;
private NanoBrain brainInstance;
public Nucleus root {
get {
return brainInstance.root;
}
}
public Nucleus root => brainInstance.root;
public NanoBrain brain {
get {
if (brainInstance == null && defaultBrain != null) {
brainInstance = Instantiate(defaultBrain);
brainInstance.name = defaultBrain.name + " (Instance)";
SwarmControl sc = FindFirstObjectByType<SwarmControl>();
UpdateWeight(brainInstance, "Avoidance", sc.avoidanceForce);
UpdateWeight(brainInstance, "Cohesion", sc.cohesionForce);
UpdateWeight(brainInstance, "Separation", sc.separationForce);
UpdateWeight(brainInstance, "Alignment", sc.alignmentForce);
}
return brainInstance;
}
}
public static void UpdateWeight(NanoBrain brain, string name, float weight) {
Nucleus root = brain.root;
foreach (Synapse synapse in root.synapses) {
if (synapse.nucleus.name == name) {
synapse.weight = weight;
}
}
}
}

View File

@ -376,12 +376,11 @@ MonoBehaviour:
speed: 1
inertia: 0.7
alignmentForce: 0.5
cohesionForce: 2.2
separationForce: -5
cohesionForce: 0.1
separationForce: -0.1
avoidanceForce: 1
separationDistance: 0.3
perceptionDistance: 2
boundaryForce: 5
spaceSize: {x: 10, y: 10, z: 10}
boundaryWidth: {x: 1, y: 1, z: 1}
--- !u!114 &301943979

View File

@ -46,9 +46,14 @@ public class Boid : MonoBehaviour {
int thingId = neighbour.GetInstanceID();
Vector3 localPosition = this.transform.InverseTransformPoint(neighbour.transform.position);
Vector3 localPosition = this.transform.InverseTransformPoint(neighbour.transform.position);
float d = localPosition.magnitude;
if (d <= sc.separationDistance)
localPosition = localPosition.normalized * 0.01f;
else
localPosition = localPosition.normalized * (localPosition.magnitude - sc.separationDistance);
if (localPosition.sqrMagnitude > 0)
boidReceptor?.ProcessStimulus(thingId, localPosition);
boidReceptor?.ProcessStimulus(thingId, localPosition, neighbour.name);
Vector3 localVelocity = this.transform.InverseTransformVector(neighbour.velocity);
if (localVelocity.sqrMagnitude > 0)

View File

@ -13,21 +13,21 @@ public class SwarmControl_Editor : Editor {
NanoBrain[] nanoBrains = FindObjectsByType<NanoBrain>(FindObjectsSortMode.None);
foreach (NanoBrain brain in nanoBrains) {
UpdateWeight(brain, "Avoidance", swarmControl.avoidanceForce);
UpdateWeight(brain, "Cohesion", swarmControl.cohesionForce);
UpdateWeight(brain, "Separation", swarmControl.separationForce);
UpdateWeight(brain, "Alignment", swarmControl.alignmentForce);
NanoBrainComponent.UpdateWeight(brain, "Avoidance", swarmControl.avoidanceForce);
NanoBrainComponent.UpdateWeight(brain, "Cohesion", swarmControl.cohesionForce);
NanoBrainComponent.UpdateWeight(brain, "Separation", swarmControl.separationForce);
NanoBrainComponent.UpdateWeight(brain, "Alignment", swarmControl.alignmentForce);
}
Debug.Log("Updated weights");
}
}
protected void UpdateWeight(NanoBrain brain, string name, float weight) {
Nucleus root = brain.root;
foreach (Synapse synapse in root.synapses) {
if (synapse.nucleus.name == name) {
synapse.weight = weight;
}
}
}
// protected void UpdateWeight(NanoBrain brain, string name, float weight) {
// Nucleus root = brain.root;
// foreach (Synapse synapse in root.synapses) {
// if (synapse.nucleus.name == name) {
// synapse.weight = weight;
// }
// }
// }
}

View File

@ -14,7 +14,7 @@ public class SwarmControl : MonoBehaviour
// public float bodyForce = 20;
public float perceptionDistance = 1.0f;
public float boundaryForce = 2.0f;
//public float boundaryForce = 2.0f;
public Vector3 spaceSize = new (10, 10, 10);
public Vector3 boundaryWidth = Vector3.one * 1.0f;
}

File diff suppressed because it is too large Load Diff