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);