diff --git a/Assembly-CSharp-Editor.csproj b/Assembly-CSharp-Editor.csproj
index e94da31..08fa44b 100644
--- a/Assembly-CSharp-Editor.csproj
+++ b/Assembly-CSharp-Editor.csproj
@@ -49,8 +49,10 @@
+
+
diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj
index af65fe2..4067143 100644
--- a/Assembly-CSharp.csproj
+++ b/Assembly-CSharp.csproj
@@ -53,6 +53,7 @@
+
diff --git a/Assets/NanoBrain/Editor/NanoBrain_Editor.cs b/Assets/NanoBrain/Editor/NanoBrain_Editor.cs
index 79fed6a..b877b32 100644
--- a/Assets/NanoBrain/Editor/NanoBrain_Editor.cs
+++ b/Assets/NanoBrain/Editor/NanoBrain_Editor.cs
@@ -26,15 +26,15 @@ public class NanoBrain_Editor : Editor {
}
private void SelectNeuron() {
- GameObject selectedObject = ((NanoBrain)target).gameObject;
- if (!selectedObject.TryGetComponent(out Boid boid))
- return;
+ // GameObject selectedObject = ((NanoBrain)target).gameObject;
+ // if (!selectedObject.TryGetComponent(out Boid boid))
+ // return;
- Nucleus neuroid = boid.totalForce;
- this.currentNucleus = neuroid;
+ // Nucleus neuroid = boid.totalForce;
+ // this.currentNucleus = neuroid;
- BuildLayers();
- Debug.Log($"Layercount = {this.layers.Count}");
+ // BuildLayers();
+ // Debug.Log($"Layercount = {this.layers.Count}");
}
#endregion Start
@@ -57,7 +57,11 @@ public class NanoBrain_Editor : Editor {
EditorGUILayout.Vector3Field("Output Value", currentNucleus.outputValue);
if (currentNucleus.synapses.Count > 0) {
EditorGUI.indentLevel++;
- foreach ((Nucleus nucleus, float weight) in currentNucleus.synapses) {
+ //foreach ((Nucleus nucleus, float weight) in currentNucleus.synapses) {
+ foreach (Synapse synapse in currentNucleus.synapses) {
+ Nucleus nucleus = synapse.nucleus;
+ float weight = synapse.weight;
+
EditorGUI.BeginDisabledGroup(nucleus.isSleeping);
EditorGUILayout.BeginHorizontal();
@@ -83,7 +87,9 @@ public class NanoBrain_Editor : Editor {
return;
NeuroidLayer currentLayer = new() { ix = layerIx };
- foreach (Neuroid outputNeuroid in selectedNucleus.receivers) {
+ //foreach (Neuroid outputNeuroid in selectedNucleus.receivers) {
+ foreach (Receiver receiver in selectedNucleus.receivers) {
+ Nucleus outputNeuroid = receiver.nucleus;
if (outputNeuroid != null) {
AddToLayer(currentLayer, outputNeuroid);
// Debug.Log($"layer {layerIx} nucleus {outputNeuroid.name}");
@@ -102,7 +108,9 @@ public class NanoBrain_Editor : Editor {
layerIx++;
currentLayer = new() { ix = layerIx };
- foreach (Nucleus input in selectedNucleus.synapses.Keys) {
+ //foreach (Nucleus input in selectedNucleus.synapses.Keys) {
+ foreach (Synapse synapse in selectedNucleus.synapses) {
+ Nucleus input = synapse.nucleus;
AddToLayer(currentLayer, input);
// Debug.Log($"layer {layerIx} nucleus {input.name}");
}
@@ -151,7 +159,10 @@ public class NanoBrain_Editor : Editor {
float inputSpacing = 400f / layerNeuroid.synapses.Count;
float inputMargin = 10 + inputSpacing / 2;
int minStale = 10000;
- foreach ((Nucleus nucleus, float weight) in layerNeuroid.synapses) {
+ //foreach ((Nucleus nucleus, float weight) in layerNeuroid.synapses) {
+ foreach (Synapse synapse in layerNeuroid.synapses) {
+ Nucleus nucleus = synapse.nucleus;
+ float weight = synapse.weight;
if (this.neuroidPositions.ContainsKey(nucleus)) {
Vector2Int inputNeuroidPos = this.neuroidPositions[nucleus];
if (inputNeuroidPos.x == layerNeuroidPos.x + 1) {
diff --git a/Assets/NanoBrain/Editor/NeuroidWindow.cs b/Assets/NanoBrain/Editor/NeuroidWindow.cs
index a725c97..b3e17a2 100644
--- a/Assets/NanoBrain/Editor/NeuroidWindow.cs
+++ b/Assets/NanoBrain/Editor/NeuroidWindow.cs
@@ -40,7 +40,9 @@ public class GraphEditorWindow : EditorWindow {
return;
NeuroidLayer currentLayer = new() { ix = layerIx };
- foreach (Nucleus outputNeuroid in selectedNucleus.receivers) {
+ //foreach (Nucleus outputNeuroid in selectedNucleus.receivers) {
+ foreach (Receiver receiver in selectedNucleus.receivers) {
+ Nucleus outputNeuroid = receiver.nucleus;
if (outputNeuroid != null) {
AddToLayer(currentLayer, outputNeuroid);
Debug.Log($"layer {layerIx} nucleus {outputNeuroid.name}");
@@ -64,7 +66,9 @@ public class GraphEditorWindow : EditorWindow {
// Debug.Log($"Synapse {six}");
// Nucleus input = synapse.neuroid;
//foreach ((Nucleus input, Synapse synapse) in selectedNucleus.synapses) {
- foreach ((Nucleus input, float weight) in selectedNucleus.synapses) {
+ //foreach ((Nucleus input, float weight) in selectedNucleus.synapses) {
+ foreach (Synapse synapse in selectedNucleus.synapses) {
+ Nucleus input = synapse.nucleus;
if (input != null) {
AddToLayer(currentLayer, input);
Debug.Log($"layer {layerIx} nucleus {input.name}");
@@ -104,8 +108,10 @@ public class GraphEditorWindow : EditorWindow {
// If the output neuroid is visited
// Note: this does not yet work for multiple outputs yet (see the use of First())
+ // if (neuroid.receivers.Count == 0 // make sure the root neuroids are processed directly
+ // || (neuronVisited.Contains(neuroid.receivers.First()) && neuroid.receivers.First().layerIx == layerIx - 1)) {
if (neuroid.receivers.Count == 0 // make sure the root neuroids are processed directly
- || (neuronVisited.Contains(neuroid.receivers.First()) && neuroid.receivers.First().layerIx == layerIx - 1)) {
+ || (neuronVisited.Contains(neuroid.receivers.First().nucleus) && neuroid.receivers.First().nucleus.layerIx == layerIx - 1)) {
// Add it to the next layer
currentLayer.neuroids.Add(neuroid);
neuroid.layerIx = layerIx;
@@ -182,7 +188,10 @@ public class GraphEditorWindow : EditorWindow {
// Vector2Int inputNeuroidPos = this.neuroidPositions[synapse.neuroid];
//foreach ((Nucleus neuroid, Synapse synapse) in layerNeuroid.synapses) {
- foreach ((Nucleus neuroid, float weight) in layerNeuroid.synapses) {
+ //foreach ((Nucleus neuroid, float weight) in layerNeuroid.synapses) {
+ foreach (Synapse synapse in layerNeuroid.synapses) {
+ Nucleus neuroid = synapse.nucleus;
+ float weight = synapse.weight;
if (neuroid != null) {
if (this.neuroidPositions.ContainsKey(neuroid)) {
Vector2Int inputNeuroidPos = this.neuroidPositions[neuroid];
@@ -272,17 +281,17 @@ public class GraphEditorWindow : EditorWindow {
if (boid == null)
return;
- Nucleus neuroid = boid.behaviour;
- this.currentNucleus = neuroid;
- if (neuroid == null)
- this.allNeuroids = new();
- else
- this.allNeuroids = neuroid.brain.neuroids;
+ // Nucleus neuroid = boid.behaviour;
+ // this.currentNucleus = neuroid;
+ // if (neuroid == null)
+ // this.allNeuroids = new();
+ // else
+ // this.allNeuroids = neuroid.brain.neuroids;
- Debug.Log($"Neuroncount = {this.allNeuroids.Count}");
- BuildLayers();
- Debug.Log($"Layercount = {this.layers.Count}");
+ // Debug.Log($"Neuroncount = {this.allNeuroids.Count}");
+ // BuildLayers();
+ // Debug.Log($"Layercount = {this.layers.Count}");
}
diff --git a/Assets/NanoBrain/Neuroid.cs b/Assets/NanoBrain/Neuroid.cs
index 33bb553..3c45ddd 100644
--- a/Assets/NanoBrain/Neuroid.cs
+++ b/Assets/NanoBrain/Neuroid.cs
@@ -5,7 +5,7 @@ public class Neuroid : Nucleus {
public bool inverse = false;
public float exponent = 1.0f;
- public Neuroid(NanoBrain brain, string name) : base(name) {
+ public Neuroid(NanoBrain brain, string name) : base(null, name) {
this.brain = brain;
if (this.brain != null)
this.brain.neuroids.Add(this);
@@ -13,43 +13,46 @@ public class Neuroid : Nucleus {
Debug.LogError("No neuroid network");
}
- public Neuroid(NanoBrainObj brain, string name) : base(name) {
- this.newBrain = brain;
- if (this.newBrain != null)
- this.newBrain.neuroids.Add(this);
- else
- Debug.LogError("No neuroid network");
+ public Neuroid(NanoBrainObj brain, string name) : base(brain, name) {
}
public void SetWeight(Neuroid input, float weight) {
- this.synapses[input] = weight;
+ //this.synapses[input] = weight;
+ this.SetWeight((Nucleus)input, weight);
}
- public void GetInputFrom(Neuroid input, float weight = 1.0f) {
- input.AddReceiver(this);
- this.synapses[input] = weight;
- }
+ // public void GetInputFrom(Neuroid input, float weight = 1.0f) {
+ // input.AddReceiver(this);
+ // //this.synapses[input] = weight;
+ // this.SetWeight((Nucleus)input, weight);
+ // }
public void SetInput(Neuroid input) {
- if (this.synapses.ContainsKey(input) == false)
- this.synapses[input] = 1.0f;
+ // if (this.synapses.ContainsKey(input) == false)
+ // this.synapses[input] = 1.0f;
+ if (this.SynapseExists(input))
+ this.SetWeight(input, 1.0f);
UpdateState();
}
public void SetInput(Neuroid input, float weight) {
- this.synapses[input] = weight;
+ //this.synapses[input] = weight;
+ this.SetWeight(input, weight);
UpdateState();
}
public virtual void UpdateState() {
Vector3 result = Vector3.zero;
- foreach ((Nucleus nucleus, float weight) in this.synapses) {
+ //foreach ((Nucleus nucleus, float weight) in this.synapses) {
+ foreach (Synapse synapse in this.synapses) {
+ Nucleus nucleus = synapse.nucleus;
if (nucleus is Neuroid neuroid && neuroid.isSleeping)
continue;
-
+
Vector3 direction = nucleus.outputValue.normalized;
float magnitude = nucleus.outputValue.magnitude;
+ float weight = synapse.weight;
magnitude = weight * Mathf.Pow(magnitude, exponent);
if (inverse && magnitude > 0)
magnitude = 1 / magnitude;
@@ -62,8 +65,10 @@ public class Neuroid : Nucleus {
this.outputValue = result;
this.stale = 0;
- foreach (Neuroid receiver in this.receivers)
- receiver.SetInput(this);
+ foreach (Receiver receiver in this.receivers) {
+ if (receiver.nucleus is Neuroid neuroid)
+ neuroid.SetInput(this);
+ }
}
// public bool IsStale() {
diff --git a/Assets/NanoBrain/Nucleus.cs b/Assets/NanoBrain/Nucleus.cs
index e5646e7..034e304 100644
--- a/Assets/NanoBrain/Nucleus.cs
+++ b/Assets/NanoBrain/Nucleus.cs
@@ -1,35 +1,169 @@
using System.Collections.Generic;
using UnityEngine;
+[System.Serializable]
public class Nucleus {
- public int stale = 0;
+
+ public int id; // hash code
+
+ [SerializeField]
+ protected string _name;
+ public virtual string name {
+ get => _name;
+ set => _name = value;
+ }
+
+ //public readonly Dictionary synapses = new();
+ [SerializeField]
+ public List synapses = new();
+ //public HashSet receivers = new();
+ [SerializeField]
+ public List receivers = new();
+
+ #region Serialization
+
+ public void Rebuild(NanoBrainObj brain) {
+ if (this.synapses != null) {
+ foreach (Synapse synapse in synapses)
+ synapse.Rebuild(brain);
+ }
+ if (this.receivers == null)
+ this.receivers = new();
+ else
+ foreach (Receiver receiver in receivers)
+ receiver.Rebuild(brain);
+ }
+
+ #endregion
+
+ #region Runtime state (not serialized)
public NanoBrain brain { get; protected set; }
public NanoBrainObj newBrain { get; protected set; }
- public virtual string name { get; set; }
-
- public readonly Dictionary synapses = new();
- public HashSet receivers = new();
public virtual Vector3 outputValue { get; set; }
+ [System.NonSerialized]
+ public int stale = 0;
+ [System.NonSerialized]
public int layerIx;
- public Nucleus(string name) {
- this.name = name;
+ #endregion Runtime state
+
+ public Nucleus(NanoBrainObj brain, string name) {
+ this.newBrain = brain;
+ if (this.newBrain != null)
+ this.newBrain.nuclei.Add(this);
+ else
+ Debug.LogError("No neuroid network");
+
+ this._name = name;
+ this.id = this.GetHashCode();
}
public virtual void AddReceiver(Nucleus receiver) {
- //Debug.Log($"add receiver to {this} for {receiver} {receiver.GetHashCode()} {this.receivers.Count} {receiver.synapses.Count}");
- this.receivers.Add(receiver);
- receiver.synapses[this] = 1.0f; // new(this);
+ this.receivers.Add(new Receiver(receiver));
+ //receiver.synapses[this] = 1.0f; // new(this);
+ receiver.SetWeight(this, 1.0f);
//Debug.Log($"receiver # {this.receivers.Count} synapse count {receiver.synapses.Count}");
}
+ public static void Delete(Nucleus nucleus) {
+ foreach (Synapse synapse in nucleus.synapses) {
+ if (synapse.nucleus.receivers.Count > 1) {
+ // there is another nucleus feeding into this input nucleus
+ synapse.nucleus.receivers.RemoveAll(r => r.nucleus == nucleus);
+ } else {
+ // No other links, delete it.
+ Nucleus.Delete(synapse.nucleus);
+ }
+ }
+ foreach (Receiver receiver in nucleus.receivers)
+ receiver.nucleus.synapses.RemoveAll(s => s.nucleus == nucleus);
+
+ nucleus.newBrain.nuclei.RemoveAll(n => n == nucleus);
+ }
+
+ public void GetInputFrom(Nucleus input, float weight = 1.0f) {
+ input.AddReceiver(this);
+ this.SetWeight(input, weight);
+ }
+
public bool isSleeping {
get {
return this.stale > 2;
}
}
+ public bool SynapseExists(Nucleus nucleus) {
+ foreach (Synapse synapse in synapses) {
+ if (synapse.nucleus == nucleus)
+ return true;
+ }
+ return false;
+ }
+
+ public void SetWeight(Nucleus nucleus, float weight) {
+ foreach (Synapse synapse in synapses) {
+ if (synapse.nucleus == nucleus) {
+ synapse.weight = weight;
+ return;
+ }
+ }
+ Synapse newSynapse = new(nucleus, weight);
+ synapses.Add(newSynapse);
+ }
+}
+
+[System.Serializable]
+public class Synapse {
+ [System.NonSerialized]
+ public Nucleus nucleus;
+ public int nucleusId;
+ public float weight;
+
+ public Synapse(Nucleus nucleus, float weight) {
+ this.nucleus = nucleus;
+ this.nucleusId = nucleus.id;
+ this.weight = weight;
+ }
+
+ public void Rebuild(NanoBrainObj brain) {
+ if (brain == null) {
+ return;
+ }
+
+ foreach (Nucleus nucleus in brain.nuclei) {
+ if (nucleus.id == this.nucleusId) {
+ this.nucleus = nucleus;
+ return;
+ }
+ }
+ Debug.LogError($"Synapse deserialization error: could not find nucleus with id {this.nucleusId}");
+ }
+}
+
+[System.Serializable]
+public class Receiver {
+ [System.NonSerialized]
+ public Nucleus nucleus;
+ public int nucleusId;
+
+ public Receiver(Nucleus nucleus) {
+ this.nucleus = nucleus;
+ this.nucleusId = nucleus.id;
+ }
+
+ public void Rebuild(NanoBrainObj brain) {
+ if (brain == null)
+ return;
+
+ foreach (Nucleus nucleus in brain.nuclei) {
+ if (nucleus.id == this.nucleusId) {
+ this.nucleus = nucleus;
+ return;
+ }
+ }
+ Debug.LogError($"Synapse deserialization error: could not find nucleus with id {this.nucleusId}");
+ }
}
\ No newline at end of file
diff --git a/Assets/NanoBrain/Perception.cs b/Assets/NanoBrain/Perception.cs
index 3353d62..11f917b 100644
--- a/Assets/NanoBrain/Perception.cs
+++ b/Assets/NanoBrain/Perception.cs
@@ -7,19 +7,26 @@ public class Perception : Nucleus {
public class Receiver {
public int thingType = 0;
- public Neuroid neuroid;
+ public Nucleus neuroid;
}
public HashSet positionReceivers { get; protected set; }
public HashSet velocityReceivers { get; protected set; }
- public Perception(NanoBrain neuroidNet) : base("Perception") {
+ public Perception(NanoBrainObj brain) : base(brain, "Perception") {
+ //this.brain = brain;
+ this.positionReceivers = new();
+ this.velocityReceivers = new();
+ }
+
+
+ public Perception(NanoBrain neuroidNet) : base(null, "Perception") {
this.brain = neuroidNet;
this.positionReceivers = new();
this.velocityReceivers = new();
}
- public void SendPositions(Neuroid receivingNeuroid, int thingType = 0, float weight = 1.0f) {
+ public void SendPositions(Nucleus receivingNeuroid, int thingType = 0, float weight = 1.0f) {
Receiver receiver = new() {
thingType = thingType,
neuroid = receivingNeuroid
@@ -28,11 +35,11 @@ public class Perception : Nucleus {
foreach (SensoryNeuroid neuroid in sensoryNeuroids) {
if (neuroid != null) {
neuroid.AddReceiver(receivingNeuroid);
- receivingNeuroid.synapses[neuroid] = weight;
+ receivingNeuroid.SetWeight(neuroid, weight);
}
}
}
- public void SendVelocities(Neuroid receivingNeuroid, int thingType = 0, float weight = 1.0f) {
+ public void SendVelocities(Nucleus receivingNeuroid, int thingType = 0, float weight = 1.0f) {
Receiver receiver = new() {
thingType = thingType,
neuroid = receivingNeuroid
@@ -41,7 +48,7 @@ public class Perception : Nucleus {
foreach (SensoryNeuroid neuroid in sensoryNeuroids) {
if (neuroid != null && neuroid.velocityNeuroid != null) {
neuroid.velocityNeuroid.AddReceiver(receivingNeuroid);
- receivingNeuroid.synapses[neuroid] = weight;
+ receivingNeuroid.SetWeight(neuroid, weight);
}
}
}
diff --git a/Assets/NanoBrain/SensoryNeuroid.cs b/Assets/NanoBrain/SensoryNeuroid.cs
index 02b6865..0129717 100644
--- a/Assets/NanoBrain/SensoryNeuroid.cs
+++ b/Assets/NanoBrain/SensoryNeuroid.cs
@@ -58,7 +58,10 @@ public class SensoryNeuroid : Neuroid {
public override void UpdateState() {
Vector3 result = receptor.localPosition;
- foreach ((Nucleus nucleus, float weight) in this.synapses) {
+ //foreach ((Nucleus nucleus, float weight) in this.synapses) {
+ foreach (Synapse synapse in this.synapses) {
+ Nucleus nucleus = synapse.nucleus;
+ float weight = synapse.weight;
Vector3 direction = nucleus.outputValue.normalized;
float magnitude = nucleus.outputValue.magnitude;
@@ -71,8 +74,10 @@ public class SensoryNeuroid : Neuroid {
result /= this.synapses.Count + 1;
this.outputValue = result;
- foreach (Neuroid neuroid in this.receivers)
- neuroid.SetInput(this);
+ //foreach (Neuroid neuroid in this.receivers)
+ foreach (Receiver receiver in this.receivers)
+ if (receiver.nucleus is Neuroid neuroid)
+ neuroid.SetInput(this);
this.stale = 0;
}
}
@@ -87,14 +92,15 @@ public class VelocityNeuroid : Neuroid {
public void Replace(string name = "velocity") {
this.name = name;
- this.receivers = new();
+ this.receivers = new();
this.lastPosition = Vector3.zero;
this.lastValueTime = 0;
}
public override void UpdateState() {
// Assuming only one synapse for now....
- Vector3 currentPosition = this.synapses.First().Key.outputValue;
+ //Vector3 currentPosition = this.synapses.First().Key.outputValue;
+ Vector3 currentPosition = this.synapses.First().nucleus.outputValue;
float currentValueTime = Time.time;
if (lastValueTime != 0) {
@@ -106,8 +112,11 @@ public class VelocityNeuroid : Neuroid {
this.outputValue = velocity;
this.stale = 0;
- foreach (Neuroid receiver in receivers)
- receiver?.SetInput(this);
+ //foreach (Neuroid receiver in receivers)
+ foreach(Receiver receiver in receivers) {
+ if (receiver.nucleus is Neuroid neuroid)
+ neuroid.SetInput(this);
+ }
}
this.lastValueTime = currentValueTime;
diff --git a/Assets/NanoBrain/VisualEditor/Editor/NanoBrainComponent_Editor.cs b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainComponent_Editor.cs
new file mode 100644
index 0000000..e762a1e
--- /dev/null
+++ b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainComponent_Editor.cs
@@ -0,0 +1,94 @@
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+[CustomEditor(typeof(NanoBrainComponent))]
+public class NanoBrainComponent_Editor : Editor {
+ protected static VisualElement mainContainer;
+ protected static VisualElement inspectorContainer;
+
+ private SerializedProperty brainProp;
+
+ public void OnEnable() {
+ brainProp = serializedObject.FindProperty(nameof(NanoBrainComponent.brain));
+ }
+
+ public override VisualElement CreateInspectorGUI() {
+ NanoBrainComponent component = target as NanoBrainComponent;
+ NanoBrainObj brain = component.brain;
+
+ serializedObject.Update();
+
+
+ VisualElement root = new();
+ root.style.flexDirection = FlexDirection.Column; // side-by-side layout
+ root.style.flexGrow = 1;
+ root.style.minHeight = 600;
+ root.style.paddingLeft = 0;
+ root.style.paddingRight = 0;
+ root.style.paddingTop = 0;
+ root.style.paddingBottom = 0;
+
+ root.styleSheets.Add(Resources.Load("GraphStyles"));
+
+ PropertyField brainField = new(brainProp);
+ brainField.label = "Nano Brain";
+ root.Add(brainField);
+
+ mainContainer = new() {
+ name = "main",
+ style = {
+ flexDirection = FlexDirection.Row,
+ flexGrow = 1,
+ minHeight = 500,
+ }
+ };
+ NanoBrainInspector.GraphView board;
+ board = new NanoBrainInspector.GraphView();
+ board.style.flexGrow = 1;
+
+ inspectorContainer = new VisualElement {
+ name = "inspector",
+ style = {
+ width = 400,
+ }
+ };
+
+ mainContainer.Add(board);
+ mainContainer.Add(inspectorContainer);
+ root.Add(mainContainer);
+
+ // Run once for initial state (use resolved style width if available)
+ float initialWidth = root.layout.width > 0 ? root.layout.width : root.contentRect.width;
+ UpdateLayout(initialWidth);
+
+ // React to size changes of root (or parent if appropriate)
+ root.RegisterCallback(evt => {
+ UpdateLayout(evt.newRect.width);
+ });
+
+ if (brain != null)
+ board.SetGraph(brain, brain.root, inspectorContainer);
+ else
+ Debug.LogWarning(" No brain!");
+
+ serializedObject.ApplyModifiedProperties();
+ return root;
+ }
+
+ private void UpdateLayout(float containerWidth) {
+ if (containerWidth > 800f) {
+ mainContainer.style.flexDirection = FlexDirection.Row;
+ inspectorContainer.style.width = 400; // fixed sidebar width
+ inspectorContainer.style.flexGrow = 0;
+ }
+ else {
+ mainContainer.style.flexDirection = FlexDirection.Column;
+ inspectorContainer.style.width = Length.Percent(100); // full width below
+ inspectorContainer.style.flexDirection = FlexDirection.Column;
+ inspectorContainer.style.flexGrow = 1; // can set 0 or keep as needed
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/Assets/NanoBrain/VisualEditor/Editor/NanoBrainComponent_Editor.cs.meta b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainComponent_Editor.cs.meta
new file mode 100644
index 0000000..eaf830b
--- /dev/null
+++ b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainComponent_Editor.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: f05072314d39990639a2dbf99f322664
\ No newline at end of file
diff --git a/Assets/NanoBrain/VisualEditor/Editor/NanoBrainEditor.cs b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainEditor.cs
index c496504..2e38c69 100644
--- a/Assets/NanoBrain/VisualEditor/Editor/NanoBrainEditor.cs
+++ b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainEditor.cs
@@ -1,6 +1,7 @@
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
+using UnityEditor.Callbacks;
using System.Linq;
using System.Collections.Generic;
@@ -10,7 +11,7 @@ public class NucleusLayer {
}
public class NanoBrainEditor : EditorWindow {
- public static NanoBrainObj brain;
+ public NanoBrainObj brain;
public static VisualElement inspectorContainer;
@@ -19,20 +20,28 @@ public class NanoBrainEditor : EditorWindow {
GetWindow("NanoBrain Editor");
}
+ public static void Open(NanoBrainObj asset) {
+ NanoBrainEditor editor = GetWindow("NanoBrain Editor");
+ editor.brain = asset;
+ editor.Show();
+ }
+
GraphBoardView board;
private void OnEnable() {
+ OnFocus();
+ }
+ private void OnFocus() {
if (brain == null) {
- brain = CreateInstance();
- EditorUtility.SetDirty(brain);
+ // brain = CreateInstance();
+ // EditorUtility.SetDirty(brain);
+ return;
}
VisualElement root = rootVisualElement;
+ root.Clear();
root.styleSheets.Add(Resources.Load("GraphStyles"));
- root.Add(board);
- root.Add(inspectorContainer);
-
VisualElement main = new() {
name = "main",
style = {
@@ -53,11 +62,15 @@ public class NanoBrainEditor : EditorWindow {
main.Add(inspectorContainer);
root.Add(main);
- board.SetGraph(brain.rootNucleus);
+ board.SetGraph(brain, brain.root);
+
}
+
}
public class GraphBoardView : VisualElement {
+ NanoBrainObj brain;
+ SerializedObject serializedBrain;
Nucleus currentNucleus;
private List layers = new();
private Dictionary neuroidPositions = new();
@@ -86,7 +99,9 @@ public class GraphBoardView : VisualElement {
RegisterCallback(OnMouseUp);
}
- public void SetGraph(Nucleus nucleus) {
+ public void SetGraph(NanoBrainObj brain, Nucleus nucleus) {
+ this.brain = brain;
+ this.serializedBrain = new SerializedObject(brain);
this.currentNucleus = nucleus;
Rebuild();
}
@@ -101,7 +116,7 @@ public class GraphBoardView : VisualElement {
if (currentWrapper != null)
Object.DestroyImmediate(currentWrapper);
- currentWrapper = ScriptableObject.CreateInstance().Init(currentNucleus, NanoBrainEditor.brain);
+ currentWrapper = ScriptableObject.CreateInstance().Init(currentNucleus, brain);
DrawInspector();
}
@@ -115,7 +130,9 @@ public class GraphBoardView : VisualElement {
return;
NeuroidLayer currentLayer = new() { ix = layerIx };
- foreach (Nucleus outputNeuroid in selectedNucleus.receivers) {
+ //foreach (Nucleus outputNeuroid in selectedNucleus.receivers) {
+ foreach (Receiver receiver in selectedNucleus.receivers) {
+ Nucleus outputNeuroid = receiver.nucleus;
if (outputNeuroid != null) {
AddToLayer(currentLayer, outputNeuroid);
// Debug.Log($"layer {layerIx} nucleus {outputNeuroid.name}");
@@ -134,7 +151,9 @@ public class GraphBoardView : VisualElement {
layerIx++;
currentLayer = new() { ix = layerIx };
- foreach (Nucleus input in selectedNucleus.synapses.Keys) {
+ //foreach (Nucleus input in selectedNucleus.synapses.Keys) {
+ foreach (Synapse synapse in selectedNucleus.synapses) {
+ Nucleus input = synapse.nucleus;
AddToLayer(currentLayer, input);
// Debug.Log($"layer {layerIx} nucleus {input.name}");
}
@@ -144,6 +163,8 @@ public class GraphBoardView : VisualElement {
}
private void AddToLayer(NeuroidLayer layer, Nucleus nucleus) {
+ if (nucleus == null)
+ return;
layer.neuroids.Add(nucleus);
nucleus.layerIx = layer.ix;
// Store its position
@@ -186,10 +207,13 @@ public class GraphBoardView : VisualElement {
if (currentNucleus == null)
return;
+ serializedBrain.Update();
+
Handles.BeginGUI();
foreach (NeuroidLayer layer in layers)
DrawLayer(layer);
Handles.EndGUI();
+
}
private void DrawLayer(NeuroidLayer layer) {
@@ -212,19 +236,24 @@ public class GraphBoardView : VisualElement {
float inputSpacing = 400f / layerNucleus.synapses.Count;
float inputMargin = 10 + inputSpacing / 2;
int minStale = 10000;
- foreach ((Nucleus nucleus, float weight) in layerNucleus.synapses) {
- if (this.neuroidPositions.ContainsKey(nucleus)) {
- Vector2Int inputNeuroidPos = this.neuroidPositions[nucleus];
- if (inputNeuroidPos.x == layerNeuroidPos.x + 1) {
- Vector3 pos = new(100 + inputNeuroidPos.x * 100, inputMargin + inputNeuroidPos.y * inputSpacing, 0.0f);
+ //foreach ((Nucleus nucleus, float weight) in layerNucleus.synapses) {
+ foreach (Synapse synapse in layerNucleus.synapses) {
+ Nucleus nucleus = synapse.nucleus;
+ if (nucleus != null) {
+ float weight = synapse.weight;
+ if (this.neuroidPositions.ContainsKey(nucleus)) {
+ Vector2Int inputNeuroidPos = this.neuroidPositions[nucleus];
+ if (inputNeuroidPos.x == layerNeuroidPos.x + 1) {
+ Vector3 pos = new(100 + inputNeuroidPos.x * 100, inputMargin + inputNeuroidPos.y * inputSpacing, 0.0f);
- float brightness = weight / 10.0f;
- Handles.color = new Color(brightness, brightness, brightness);
- Handles.DrawLine(parentPos, pos);
+ float brightness = weight / 10.0f;
+ Handles.color = new Color(brightness, brightness, brightness);
+ Handles.DrawLine(parentPos, pos);
+ }
}
+ if (nucleus is Neuroid neuroid && neuroid.stale < minStale)
+ minStale = neuroid.stale;
}
- if (nucleus is Neuroid neuroid && neuroid.stale < minStale)
- minStale = neuroid.stale;
}
if (layerNucleus.synapses.Count > 0 && minStale > 2 && layerNucleus.stale < 3)
@@ -255,7 +284,7 @@ public class GraphBoardView : VisualElement {
// Process Hover
HandleMouseHover(layerNucleus, neuronRect);
// Process click
- Debug.Log($"{et} {e.type}");
+ // Debug.Log($"{et} {e.type}");
if (e.type == EventType.MouseDown && e.button == 0) {
// Consume the event so the scene doesn't also handle it
e.Use();
@@ -298,6 +327,9 @@ public class GraphBoardView : VisualElement {
void DrawInspector() {
+ if (NanoBrainEditor.inspectorContainer == null)
+ return;
+
NanoBrainEditor.inspectorContainer.Clear();
if (this.currentNucleus == null)
return;
@@ -315,18 +347,20 @@ public class GraphBoardView : VisualElement {
EditorGUILayout.LabelField("Synapses");
EditorGUI.indentLevel++;
- List nuclei = currentNucleus.synapses.Keys.ToList();
- foreach (Nucleus nucleus in nuclei) {
- EditorGUI.BeginDisabledGroup(nucleus.isSleeping);
+ //List nuclei = currentNucleus.synapses.Keys.ToList();
+ // foreach (Nucleus nucleus in nuclei) {
+ foreach (Synapse synapse in currentNucleus.synapses) {
+ EditorGUI.BeginDisabledGroup(synapse.nucleus.isSleeping);
EditorGUILayout.BeginHorizontal();
- EditorGUILayout.LabelField(nucleus.name, GUILayout.Width(120));
+ EditorGUILayout.LabelField(synapse.nucleus.name, GUILayout.Width(120));
EditorGUI.indentLevel--;
EditorGUILayout.LabelField("Weight", GUILayout.Width(45));
- float weight = currentNucleus.synapses[nucleus];
- currentNucleus.synapses[nucleus] = EditorGUILayout.FloatField(weight, GUILayout.Width(40));
+ // float weight = currentNucleus.synapses[nucleus];
+ // currentNucleus.synapses[nucleus] = EditorGUILayout.FloatField(weight, GUILayout.Width(40));
+ synapse.weight = EditorGUILayout.FloatField(synapse.weight, GUILayout.Width(40));
EditorGUI.indentLevel++;
- EditorGUILayout.Vector3Field(GUIContent.none, nucleus.outputValue, GUILayout.Width(180));
+ EditorGUILayout.Vector3Field(GUIContent.none, synapse.nucleus.outputValue, GUILayout.Width(180));
EditorGUILayout.EndHorizontal();
EditorGUI.EndDisabledGroup();
@@ -334,14 +368,15 @@ public class GraphBoardView : VisualElement {
EditorGUI.indentLevel--;
}
if (GUILayout.Button("Add Neuron"))
- AddInputNeuron(currentNucleus);
+ AddInputNeuron(currentNucleus);
});
+
NanoBrainEditor.inspectorContainer.Add(container);
}
protected virtual void AddInputNeuron(Nucleus receiver) {
- Neuroid newNeuroid = new(NanoBrainEditor.brain, "New neuron");
+ Neuroid newNeuroid = new(brain, "New neuron");
newNeuroid.AddReceiver(receiver);
Rebuild();
}
@@ -363,21 +398,6 @@ public class GraphBoardView : VisualElement {
return parentPos;
}
- // helper to save node position back to graph
- // public void UpdateNodePosition(NucleusObj node, Vector2 worldPos) {
- // Undo.RecordObject(graph, "Move Node");
- // node.position = (worldPos - pan) / zoom;
- // EditorUtility.SetDirty(graph);
- // }
-
- // create & delete APIs called by NodeView or toolbar
- public void CreateNode(Vector2 worldPos) {
- // Undo.RecordObject(graph, "Create Node");
- // var n = new NucleusObj("New Node"); //, position = (worldPos - pan) / zoom };
- // graph.nodes.Add(n);
- // EditorUtility.SetDirty(graph);
- // Rebuild();
- }
// public void CreateEdge(string fromId, string toId) {
// if (fromId == toId) return;
@@ -469,9 +489,23 @@ public class GraphNodeWrapper : ScriptableObject {
node.name = title;
//node.position = position;
#if UNITY_EDITOR
- UnityEditor.EditorUtility.SetDirty(graph);
+ if (graph != null)
+ UnityEditor.EditorUtility.SetDirty(graph);
#endif
}
}
}
-//static class EdgeDragState { public static EdgeDragState active; public GraphNode fromNode; public bool fromIsOutput; }
\ No newline at end of file
+//static class EdgeDragState { public static EdgeDragState active; public GraphNode fromNode; public bool fromIsOutput; }
+
+public static class OpenAssetHandler {
+ // Called when an asset is double-clicked or opened.
+ [OnOpenAsset]
+ public static bool OpenMyScriptableObject(int instanceID, int line) {
+ NanoBrainObj obj = EditorUtility.InstanceIDToObject(instanceID) as NanoBrainObj;
+ if (obj != null) {
+ NanoBrainEditor.Open(obj);
+ return true; // handled
+ }
+ return false; // let Unity open normally
+ }
+}
diff --git a/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs
new file mode 100644
index 0000000..2b36ad9
--- /dev/null
+++ b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs
@@ -0,0 +1,484 @@
+using System.Collections.Generic;
+
+using UnityEditor;
+
+using UnityEngine;
+using UnityEngine.UIElements;
+
+[CustomEditor(typeof(NanoBrainObj))]
+public class NanoBrainInspector : Editor {
+ protected static VisualElement mainContainer;
+ protected static VisualElement inspectorContainer;
+
+ //private Nucleus currentNucleus = null;
+ private List layers = new();
+ private Dictionary neuroidPositions = new();
+
+ protected bool breakOnWake = false;
+
+ #region Start
+
+ public override VisualElement CreateInspectorGUI() {
+ NanoBrainObj brain = target as NanoBrainObj;
+
+ serializedObject.Update();
+
+ VisualElement root = new();
+ root.style.flexDirection = FlexDirection.Column; // side-by-side layout
+ root.style.flexGrow = 1;
+ root.style.minHeight = 600;
+ root.style.paddingLeft = 0;
+ root.style.paddingRight = 0;
+ root.style.paddingTop = 0;
+ root.style.paddingBottom = 0;
+
+ root.styleSheets.Add(Resources.Load("GraphStyles"));
+
+ mainContainer = new() {
+ name = "main",
+ style = {
+ flexDirection = FlexDirection.Row,
+ flexGrow = 1,
+ minHeight = 500,
+ }
+ };
+ GraphView board;
+ board = new GraphView();
+ board.style.flexGrow = 1;
+
+ inspectorContainer = new VisualElement {
+ name = "inspector",
+ style = {
+ width = 400,
+ }
+ };
+
+ mainContainer.Add(board);
+ mainContainer.Add(inspectorContainer);
+ root.Add(mainContainer);
+
+ // Run once for initial state (use resolved style width if available)
+ float initialWidth = root.layout.width > 0 ? root.layout.width : root.contentRect.width;
+ UpdateLayout(initialWidth);
+
+ // React to size changes of root (or parent if appropriate)
+ root.RegisterCallback(evt => {
+ UpdateLayout(evt.newRect.width);
+ });
+
+ if (brain != null)
+ board.SetGraph(brain, brain.root, inspectorContainer);
+ else
+ Debug.LogWarning(" No brain!");
+
+ serializedObject.ApplyModifiedProperties();
+ return root;
+ }
+
+ public class GraphView : VisualElement {
+ NanoBrainObj brain;
+ SerializedObject serializedBrain;
+ Nucleus currentNucleus;
+ private List layers = new();
+ private readonly Dictionary neuroidPositions = new();
+
+ Vector2 pan = Vector2.zero;
+ //float zoom = 1f;
+ bool draggingCanvas = false;
+ Vector2 lastMouse;
+ GraphNodeWrapper currentWrapper;
+
+ public GraphView() {
+ name = "content";
+ style.flexGrow = 1;
+
+ IMGUIContainer imguiContainer = new(OnIMGUI);
+ imguiContainer.style.position = Position.Absolute;
+ imguiContainer.style.left = 0; imguiContainer.style.top = 0;
+ imguiContainer.style.right = 0; imguiContainer.style.bottom = 0;
+ imguiContainer.pickingMode = PickingMode.Position;
+ imguiContainer.focusable = true;
+ Add(imguiContainer);
+
+ //RegisterCallback(OnWheel);
+ RegisterCallback(OnMouseDown);
+ RegisterCallback(OnMouseMove);
+ RegisterCallback(OnMouseUp);
+ }
+
+ public void SetGraph(NanoBrainObj brain, Nucleus nucleus, VisualElement inspectorContainer) {
+ this.brain = brain;
+ this.serializedBrain = new SerializedObject(brain);
+ this.currentNucleus = nucleus;
+ Rebuild(inspectorContainer);
+ }
+
+ void Rebuild(VisualElement inspectorContainer) {
+ BuildLayers();
+
+ if (this.currentNucleus == null) {
+ inspectorContainer.Clear();
+ return;
+ }
+
+ if (currentWrapper != null)
+ DestroyImmediate(currentWrapper);
+ currentWrapper = CreateInstance().Init(this.currentNucleus, brain);
+ 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.receivers != null) {
+ foreach (Receiver receiver in selectedNucleus.receivers) {
+ Nucleus outputNeuroid = receiver.nucleus;
+ 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.nucleus;
+ 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;
+
+ }
+
+ // 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(); }
+ }
+ void OnMouseMove(MouseMoveEvent e) {
+ if (draggingCanvas) {
+ var delta = e.mousePosition - lastMouse;
+ pan += delta;
+ //content.style.left = pan.x;
+ //content.style.top = pan.y;
+ lastMouse = e.mousePosition;
+ }
+ }
+ void OnMouseUp(MouseUpEvent e) { if (e.button == 2) draggingCanvas = false; }
+
+ void OnIMGUI() {
+ if (currentNucleus == null)
+ return;
+
+ serializedBrain.Update();
+
+ Handles.BeginGUI();
+ foreach (NeuroidLayer layer in layers)
+ DrawLayer(layer);
+ Handles.EndGUI();
+
+ }
+
+ private void DrawLayer(NeuroidLayer layer) {
+ int nodeCount = layer.neuroids.Count;
+ float maxValue = 0;
+ foreach (Nucleus nucleus in layer.neuroids) {
+ if (nucleus is Neuroid neuroid) {
+ float value = neuroid.outputValue.magnitude;
+ if (value > maxValue)
+ maxValue = value;
+ }
+ }
+ float spacing = 400f / nodeCount;
+ float margin = 10 + spacing / 2;
+ foreach (Nucleus layerNucleus in layer.neuroids) {
+ Vector2Int layerNeuroidPos = this.neuroidPositions[layerNucleus];
+ Vector3 parentPos = new(100 + layerNeuroidPos.x * 100, margin + layerNeuroidPos.y * spacing, 0.1f);
+
+ //int i = 0;
+ float inputSpacing = 400f / layerNucleus.synapses.Count;
+ float inputMargin = 10 + inputSpacing / 2;
+ int minStale = 10000;
+ //foreach ((Nucleus nucleus, float weight) in layerNucleus.synapses) {
+ foreach (Synapse synapse in layerNucleus.synapses) {
+ Nucleus nucleus = synapse.nucleus;
+ if (nucleus != null) {
+ float weight = synapse.weight;
+ if (this.neuroidPositions.ContainsKey(nucleus)) {
+ Vector2Int inputNeuroidPos = this.neuroidPositions[nucleus];
+ if (inputNeuroidPos.x == layerNeuroidPos.x + 1) {
+ Vector3 pos = new(100 + inputNeuroidPos.x * 100, inputMargin + inputNeuroidPos.y * inputSpacing, 0.0f);
+
+ //float brightness = weight / 10.0f;
+ Handles.color = Color.white; //new Color(brightness, brightness, brightness);
+ Handles.DrawLine(parentPos, pos);
+ }
+ }
+ if (nucleus is Neuroid neuroid && neuroid.stale < minStale)
+ minStale = neuroid.stale;
+ }
+ }
+
+ // if (layerNucleus.synapses.Count > 0 && minStale > 2 && layerNucleus.stale < 3)
+ // Debug.LogWarning($"Strange {minStale} is big duing update");
+
+
+ float size = 20;
+ if (layerNucleus.isSleeping)
+ Handles.color = Color.darkRed;
+ else {
+ float brightness = layerNucleus.outputValue.magnitude / maxValue;
+ Handles.color = new Color(brightness, brightness, brightness);
+ }
+ Handles.DrawSolidDisc(parentPos, Vector3.forward, size);
+ Vector3 labelPos = parentPos - 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, layerNucleus.name, style);
+
+ Rect neuronRect = new(parentPos.x - size, parentPos.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(layerNucleus, neuronRect);
+ // Process click
+ // Debug.Log($"{et} {e.type}");
+ if (e.type == EventType.MouseDown && e.button == 0) {
+ // Consume the event so the scene doesn't also handle it
+ e.Use();
+ HandleClicked(layerNucleus);
+ }
+ }
+ }
+ }
+
+ private void HandleMouseHover(Nucleus neuroid, Rect rect) {
+ GUIContent tooltip;
+ if (neuroid is SensoryNeuroid sensoryNeuroid) {
+ tooltip = new(
+ $"{sensoryNeuroid.name}" +
+ $"\nThing {sensoryNeuroid.receptor.thingId}" +
+ $"\nValue: {neuroid.outputValue}" +
+ $"\nStale: {neuroid.stale}");
+ }
+ else {
+ tooltip = new(
+ $"{neuroid.name}" +
+ $"\nsynapse count {neuroid.synapses.Count}" +
+ $"\nValue: {neuroid.outputValue}" +
+ $"\nStale: {neuroid.stale}");
+ }
+
+ 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) {
+ this.currentNucleus = nucleus;
+ BuildLayers();
+ }
+
+
+ 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(currentWrapper);
+ IMGUIContainer container = new(() => {
+ if (so.targetObject == null)
+ return;
+ so.Update();
+ this.currentNucleus.name = EditorGUILayout.TextField(this.currentNucleus.name);
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.LabelField("Output Value", GUILayout.Width(100));
+ EditorGUILayout.Vector3Field(GUIContent.none, this.currentNucleus.outputValue);
+ EditorGUILayout.EndHorizontal();
+ if (this.currentNucleus.synapses.Count > 0) {
+ EditorGUILayout.LabelField("Synapses");
+ EditorGUI.indentLevel++;
+
+ foreach (Synapse synapse in this.currentNucleus.synapses) {
+ if (synapse.nucleus != null) {
+ EditorGUI.BeginDisabledGroup(synapse.nucleus.isSleeping);
+
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.LabelField(synapse.nucleus.name, GUILayout.Width(120));
+ EditorGUI.indentLevel--;
+ EditorGUILayout.LabelField("Weight", GUILayout.Width(45));
+ synapse.weight = EditorGUILayout.FloatField(synapse.weight, GUILayout.Width(40));
+ EditorGUI.indentLevel++;
+ EditorGUILayout.Vector3Field(GUIContent.none, synapse.nucleus.outputValue, GUILayout.Width(180));
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUI.EndDisabledGroup();
+ }
+ }
+ EditorGUI.indentLevel--;
+ }
+ if (GUILayout.Button("Add Neuron"))
+ AddInputNeuron(this.currentNucleus);
+ if (GUILayout.Button("Add Position Perception"))
+ AddPositionPerception(this.currentNucleus);
+ if (GUILayout.Button("Add Velocity Perception"))
+ AddVelocityPerception(this.currentNucleus);
+ if (GUILayout.Button("Delete this neuron"))
+ DeleteNeuron(this.currentNucleus);
+
+ });
+
+ inspectorContainer.Add(container);
+ }
+
+ protected virtual void AddInputNeuron(Nucleus receiver) {
+ Neuroid newNeuroid = new(this.brain, "New neuron");
+ newNeuroid.AddReceiver(receiver);
+ //Rebuild(inspectorContainer);
+ BuildLayers();
+ }
+
+ protected virtual void DeleteNeuron(Nucleus nucleus) {
+ this.currentNucleus = nucleus.receivers[0].nucleus;
+ Nucleus.Delete(nucleus);
+ //Rebuild(inspectorContainer);
+ BuildLayers();
+ }
+
+ protected virtual void AddPositionPerception(Nucleus receiver) {
+ this.brain.perception.SendPositions(receiver);
+ }
+ protected virtual void AddVelocityPerception(Nucleus receiver) {
+ this.brain.perception.SendVelocities(receiver);
+ }
+
+ 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
+
+ #region Update
+
+ private void UpdateLayout(float containerWidth) {
+ if (containerWidth > 800f) {
+ mainContainer.style.flexDirection = FlexDirection.Row;
+ inspectorContainer.style.width = 400; // fixed sidebar width
+ inspectorContainer.style.flexGrow = 0;
+ }
+ else {
+ mainContainer.style.flexDirection = FlexDirection.Column;
+ inspectorContainer.style.width = Length.Percent(100); // full width below
+ inspectorContainer.style.flexDirection = FlexDirection.Column;
+ inspectorContainer.style.flexGrow = 1; // can set 0 or keep as needed
+ }
+ }
+
+ 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
+}
diff --git a/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs.meta b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs.meta
new file mode 100644
index 0000000..e71178e
--- /dev/null
+++ b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: c96ad47c3d4498640b52630789e38573
\ No newline at end of file
diff --git a/Assets/NanoBrain/VisualEditor/NanoBrainComponent.cs b/Assets/NanoBrain/VisualEditor/NanoBrainComponent.cs
new file mode 100644
index 0000000..f4e7090
--- /dev/null
+++ b/Assets/NanoBrain/VisualEditor/NanoBrainComponent.cs
@@ -0,0 +1,15 @@
+using UnityEngine;
+
+public class NanoBrainComponent : MonoBehaviour {
+ public NanoBrainObj brain;
+ public Nucleus root {
+ get {
+ return brain.root;
+ }
+ }
+ public Perception perception {
+ get {
+ return brain.perception;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/NanoBrain/VisualEditor/NanoBrainComponent.cs.meta b/Assets/NanoBrain/VisualEditor/NanoBrainComponent.cs.meta
new file mode 100644
index 0000000..1666c60
--- /dev/null
+++ b/Assets/NanoBrain/VisualEditor/NanoBrainComponent.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 92f34a5e4027a1dc39efd8ce63cf6aba
\ No newline at end of file
diff --git a/Assets/NanoBrain/VisualEditor/NanoBrainObj.cs b/Assets/NanoBrain/VisualEditor/NanoBrainObj.cs
index 751854b..a870d49 100644
--- a/Assets/NanoBrain/VisualEditor/NanoBrainObj.cs
+++ b/Assets/NanoBrain/VisualEditor/NanoBrainObj.cs
@@ -1,22 +1,49 @@
using System.Collections.Generic;
using UnityEngine;
-public class NanoBrainObj : ScriptableObject {
- public Nucleus rootNucleus = new("root");
-
- public List neuroids = new();
+[CreateAssetMenu(menuName = "Passer/NanoBrain")]
+public class NanoBrainObj : ScriptableObject, ISerializationCallbackReceiver {
+
+ public string title;
+ public int count;
+ public Color color = Color.white;
+ public Texture2D texture;
+
+ public List nuclei = new();
+
+ // This is probably always the first element in the nuclei list...
+ [System.NonSerialized]
+ public Nucleus root;
+ public int rootId;
+
+ public Perception perception;
+
+ public NanoBrainObj() {
+ this.root = new(this, "Root");
+ this.perception = new Perception(this);
+ }
public Neuroid AddNeuron(string name) {
Neuroid neuroid = new(this, name);
return neuroid;
}
- public void UpdateNeurons() {
- foreach (Neuroid neuroid in neuroids) {
+ public void UpdateNuclei() {
+ foreach (Neuroid neuroid in nuclei) {
neuroid.stale++;
if (neuroid.isSleeping)
neuroid.outputValue = Vector3.zero;
}
}
+ public void OnBeforeSerialize() {
+ this.rootId = root.id;
+ }
+ public void OnAfterDeserialize() {
+ foreach (Nucleus nucleus in nuclei) {
+ if (this.rootId == nucleus.id)
+ this.root = nucleus;
+ nucleus.Rebuild(this);
+ }
+ }
}
\ No newline at end of file
diff --git a/Assets/Scenes/Boids/Boid Graph.asset b/Assets/Scenes/Boids/Boid Graph.asset
deleted file mode 100644
index bd6ef42..0000000
--- a/Assets/Scenes/Boids/Boid Graph.asset
+++ /dev/null
@@ -1,17 +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: 95e66c6366d904e98bc83428217d4fd7, type: 3}
- m_Name: Boid Graph
- m_EditorClassIdentifier: Unity.VisualScripting.Flow::Unity.VisualScripting.ScriptGraphAsset
- _data:
- _json: '{"graph":{"variables":{"Kind":"Flow","collection":{"$content":[],"$version":"A"},"$version":"A"},"controlInputDefinitions":[],"controlOutputDefinitions":[],"valueInputDefinitions":[],"valueOutputDefinitions":[],"title":null,"summary":null,"pan":{"x":0.0,"y":0.0},"zoom":1.0,"elements":[],"$version":"A"}}'
- _objectReferences: []
diff --git a/Assets/Scenes/Boids/New Nano Brain Obj.asset b/Assets/Scenes/Boids/New Nano Brain Obj.asset
new file mode 100644
index 0000000..1fc53fd
--- /dev/null
+++ b/Assets/Scenes/Boids/New Nano Brain Obj.asset
@@ -0,0 +1,28 @@
+%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: 36081359186edfec998d891a1feeb17b, type: 3}
+ m_Name: New Nano Brain Obj
+ m_EditorClassIdentifier: Assembly-CSharp::NanoBrainObj
+ title:
+ count: 0
+ color: {r: 1, g: 1, b: 1, a: 1}
+ texture: {fileID: 0}
+ nuclei:
+ - id: 257807948
+ _name: Root
+ synapses: []
+ receivers: []
+ - id: -1868865374
+ _name: Perception
+ synapses: []
+ receivers: []
+ rootId: 257807948
diff --git a/Assets/Scenes/Boids/Boid Graph.asset.meta b/Assets/Scenes/Boids/New Nano Brain Obj.asset.meta
similarity index 79%
rename from Assets/Scenes/Boids/Boid Graph.asset.meta
rename to Assets/Scenes/Boids/New Nano Brain Obj.asset.meta
index 3cefbaf..01cdcae 100644
--- a/Assets/Scenes/Boids/Boid Graph.asset.meta
+++ b/Assets/Scenes/Boids/New Nano Brain Obj.asset.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 1a94b234cb4edce40bbe7e748f0508b3
+guid: 55099766f6f09071ab4e8c89b02fa302
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
diff --git a/Assets/Scenes/Boids/Prefabs/Boid.prefab b/Assets/Scenes/Boids/Prefabs/Boid.prefab
index 89241e9..039cf86 100644
--- a/Assets/Scenes/Boids/Prefabs/Boid.prefab
+++ b/Assets/Scenes/Boids/Prefabs/Boid.prefab
@@ -123,6 +123,7 @@ GameObject:
m_Component:
- component: {fileID: 8702527964058765412}
- component: {fileID: 9169912378811971808}
+ - component: {fileID: 85370558829139006}
m_Layer: 0
m_Name: Boid
m_TagString: Untagged
@@ -158,16 +159,21 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: fe92e8a3728a1e444a25b79acf6b1d00, type: 3}
m_Name:
m_EditorClassIdentifier:
- speed: 0.2
- neighbourCount: 0
- inertia: 0
- alignmentForce: 1
- cohesionForce: 1
- separationForce: 1
- separationDistance: 0.5
- bodyForce: 1
- debug: 0
sc: {fileID: 0}
velocity: {x: 0, y: 0, z: 0}
acceleration: {x: 0, y: 0, z: 0}
+ nanoBrain: {fileID: 0}
id: 0
+--- !u!114 &85370558829139006
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 8702527964058765413}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 92f34a5e4027a1dc39efd8ce63cf6aba, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Assembly-CSharp::NanoBrainComponent
+ brain: {fileID: 11400000, guid: 55099766f6f09071ab4e8c89b02fa302, type: 2}
diff --git a/Assets/Scenes/Boids/Scripts/Boid.cs b/Assets/Scenes/Boids/Scripts/Boid.cs
index 0fb20f7..f13d7a9 100644
--- a/Assets/Scenes/Boids/Scripts/Boid.cs
+++ b/Assets/Scenes/Boids/Scripts/Boid.cs
@@ -1,6 +1,7 @@
using UnityEngine;
-[RequireComponent(typeof(NanoBrain))]
+//[RequireComponent(typeof(NanoBrain))]
+[RequireComponent(typeof(NanoBrainComponent))]
public class Boid : MonoBehaviour {
public static int BoundaryType = 1;
public static int BoidType = 2;
@@ -11,17 +12,18 @@ public class Boid : MonoBehaviour {
private Bounds innerBounds;
- public NanoBrain neuroidNet;
- public Perception perception;
+ public NanoBrainComponent nanoBrain;
- public Nucleus behaviour;
+ // public NanoBrain neuroidNet;
+ // public Perception perception;
- public Neuroid totalForce;
+ // public Nucleus behaviour;
+
+ // public Neuroid totalForce;
public int id;
void Awake() {
- neuroidNet = GetComponent();
this.id = this.GetInstanceID();
@@ -29,13 +31,14 @@ public class Boid : MonoBehaviour {
innerBounds = new(sc.transform.position, sc.spaceSize - 2 * sc.boundaryWidth);
- perception = new Perception(neuroidNet);
+ // neuroidNet = GetComponent();
+ // perception = new Perception(neuroidNet);
- //behaviour = new Roaming(neuroidNet, perception, sc);
- behaviour = new Swarming(neuroidNet, perception, sc);
+ // //behaviour = new Roaming(neuroidNet, perception, sc);
+ // behaviour = new Swarming(neuroidNet, perception, sc);
- totalForce = new(neuroidNet, "Total");
- behaviour.AddReceiver(totalForce);
+ // totalForce = new(neuroidNet, "Total");
+ // behaviour.AddReceiver(totalForce);
}
void Update() {
@@ -50,7 +53,7 @@ public class Boid : MonoBehaviour {
//Debug.DrawRay(this.transform.position, this.transform.TransformDirection(localPosition), Color.magenta);
int thingId = neighbour.GetInstanceID();
- perception.ProcessStimulus(thingId, BoidType, localPosition, neighbour.gameObject.name);
+ nanoBrain.perception.ProcessStimulus(thingId, BoidType, localPosition, neighbour.gameObject.name);
}
}
@@ -59,10 +62,11 @@ public class Boid : MonoBehaviour {
Vector3 pointOnBounds = innerBounds.ClosestPoint(point);
Vector3 desiredWorldSpace = (pointOnBounds - point).normalized * sc.speed;
Vector3 desiredLocalSpace = -this.transform.InverseTransformPoint(desiredWorldSpace);
- perception.ProcessStimulus(777, BoundaryType, desiredLocalSpace, "Boundary");
+ nanoBrain.perception.ProcessStimulus(777, BoundaryType, desiredLocalSpace, "Boundary");
}
- Vector3 worldForce = this.transform.TransformDirection(behaviour.outputValue);
+ //Vector3 worldForce = this.transform.TransformDirection(behaviour.outputValue);
+ Vector3 worldForce = this.transform.TransformDirection(nanoBrain.root.outputValue);
this.velocity = (1 - sc.inertia) * (worldForce * Time.deltaTime) + sc.inertia * velocity;
if (this.velocity.magnitude > 0)
@@ -78,7 +82,8 @@ public class Boid : MonoBehaviour {
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 2f); // Adjust the speed of rotation
}
- neuroidNet.UpdateNeurons();
+ //neuroidNet.UpdateNeurons();
+ nanoBrain.brain.UpdateNuclei();
}
}
diff --git a/Assets/Scenes/Boids/Scripts/RoamingNucleus.cs b/Assets/Scenes/Boids/Scripts/RoamingNucleus.cs
index f8a119d..2ba38b7 100644
--- a/Assets/Scenes/Boids/Scripts/RoamingNucleus.cs
+++ b/Assets/Scenes/Boids/Scripts/RoamingNucleus.cs
@@ -3,7 +3,7 @@ public class Roaming : Nucleus {
public Neuroid output;
- public Roaming(NanoBrain neuroidNet, Perception perception, SwarmControl sc) : base("Roaming nucleus") {
+ public Roaming(NanoBrain neuroidNet, Perception perception, SwarmControl sc) : base(null, "Roaming nucleus") {
avoidance = new(neuroidNet, "Avoidance") { inverse = true };
perception.SendPositions(avoidance, Boid.BoundaryType);
diff --git a/Assets/Scenes/Boids/Scripts/SwarmingNucleus.cs b/Assets/Scenes/Boids/Scripts/SwarmingNucleus.cs
index 64f675f..e0ea77b 100644
--- a/Assets/Scenes/Boids/Scripts/SwarmingNucleus.cs
+++ b/Assets/Scenes/Boids/Scripts/SwarmingNucleus.cs
@@ -10,7 +10,7 @@ public class Swarming : Nucleus {
public override Vector3 outputValue { get => output.outputValue; set => output.outputValue = value; }
- public Swarming(NanoBrain neuroidNet, Perception perception, SwarmControl sc) : base("Swarming Nucleus") {
+ public Swarming(NanoBrain neuroidNet, Perception perception, SwarmControl sc) : base(null, "Swarming Nucleus") {
this.cohesion = new(neuroidNet, "Cohesion") { inverse = false };
perception.SendPositions(this.cohesion, Boid.BoidType);