diff --git a/Editor/Brain_Editor.cs b/Editor/Brain_Editor.cs index 449a004..6a9e654 100644 --- a/Editor/Brain_Editor.cs +++ b/Editor/Brain_Editor.cs @@ -24,7 +24,6 @@ namespace NanoBrain { } public override VisualElement CreateInspectorGUI() { - Cluster brain = component.InitializeBrain(); if (Application.isPlaying == false) serializedObject.Update(); @@ -36,27 +35,23 @@ namespace NanoBrain { paddingRight = 0, paddingTop = 0, paddingBottom = 0 - } + } }; root.styleSheets.Add(Resources.Load("GraphStyles")); - //if (Application.isPlaying == false) { - PropertyField brainField = new(brainProp) { - label = "Cluster Prefab" - }; - root.Add(brainField); - //} + PropertyField brainField = new(brainProp) { + label = "Cluster Prefab" + }; + root.Add(brainField); - - if (brain != null) - CreateViewer(root, brain.prefab, brain.defaultOutput, component.gameObject); + CreateViewer(root, component.brain, component.gameObject); if (Application.isPlaying == false) serializedObject.ApplyModifiedProperties(); return root; } - public ClusterViewer.GraphView CreateViewer(VisualElement root, ClusterPrefab cluster, Nucleus output, GameObject gameObject) { + public ClusterViewer.GraphView CreateViewer(VisualElement root, Cluster cluster, GameObject gameObject) { VisualElement mainContainer = new() { style = { flexDirection = FlexDirection.Row, @@ -69,7 +64,7 @@ namespace NanoBrain { mainContainer.Add(graph); root.Add(mainContainer); - graph.SetGraph(gameObject, output); + graph.SetGraph(gameObject); return graph; } diff --git a/Editor/ClusterEditor.cs b/Editor/ClusterEditor.cs index fa860aa..1498a79 100644 --- a/Editor/ClusterEditor.cs +++ b/Editor/ClusterEditor.cs @@ -18,13 +18,13 @@ namespace NanoBrain { serializedObject.Update(); VisualElement root = new(); - CreateEditor(root, prefab, prefab.output, null); + CreateEditor(root, prefab, null); serializedObject.ApplyModifiedProperties(); return root; } - public GraphView CreateEditor(VisualElement root, ClusterPrefab cluster, Nucleus output, GameObject gameObject) { + public GraphView CreateEditor(VisualElement root, ClusterPrefab cluster, GameObject gameObject) { root.style.paddingLeft = 0; root.style.paddingRight = 0; root.style.paddingTop = 0; @@ -56,14 +56,22 @@ namespace NanoBrain { mainContainer.Add(inspectorContainer); root.Add(mainContainer); - graphContainer.SetGraph(gameObject, output, inspectorContainer); + graphContainer.SetGraph(gameObject, inspectorContainer); return graphContainer; } public class GraphEditor : GraphView { - public GraphEditor(ClusterPrefab prefab) : base(prefab) { + protected ClusterPrefab prefab; + + public GraphEditor(ClusterPrefab prefab) : base(prefab.output.parent) { + this.prefab = prefab; + + // In a Prefab editor, no instance exists but we need it for the ClusterViewer. + // So we create a temporary instance + Cluster cluster = new(prefab); + this.currentCluster = cluster; Button addButton = new(() => OnAddClusterOutput()) { text = "Add" @@ -82,20 +90,20 @@ namespace NanoBrain { this.currentNucleus = newOutput; } - public void SetGraph(GameObject gameObject, Nucleus nucleus, VisualElement inspectorContainer) { + public void SetGraph(GameObject gameObject, VisualElement inspectorContainer) { this.gameObject = gameObject; if (Application.isPlaying == false) this.serializedBrain = new SerializedObject(this.prefab); - this.currentNucleus = nucleus; + this.selectedOutput = this.currentCluster.outputs[0]; + this.currentNucleus = this.selectedOutput; + //this.currentCluster = this.currentNucleus.parent; Rebuild(inspectorContainer); - this.selectedOutput = this.prefab.outputs[0]; // if (outputsPopup != null) // OnOutputChanged(outputsPopup.choices[0]); } private void Rebuild(VisualElement inspectorContainer) { - BuildLayers(); if (this.currentNucleus == null) { inspectorContainer.Clear(); @@ -440,14 +448,12 @@ namespace NanoBrain { Neuron newNeuroid = new(this.prefab, "New neuron"); newNeuroid.AddReceiver(nucleus); this.currentNucleus = newNeuroid; - BuildLayers(); } protected virtual void AddMemoryCellInput(Nucleus nucleus) { MemoryCell newMemory = new(this.prefab, "New memory cell"); newMemory.AddReceiver(nucleus); this.currentNucleus = newMemory; - BuildLayers(); } protected virtual void AddClusterInput(Nucleus nucleus) { @@ -540,7 +546,6 @@ namespace NanoBrain { Neuron.Delete(nucleus); this.currentNucleus = this.prefab.output; - BuildLayers(); } Nucleus.Type selectedType = Nucleus.Type.None; diff --git a/Editor/ClusterViewer.cs b/Editor/ClusterViewer.cs index 2796d01..1af3311 100644 --- a/Editor/ClusterViewer.cs +++ b/Editor/ClusterViewer.cs @@ -12,14 +12,13 @@ namespace NanoBrain { public static ClusterPrefab previousPrefab; public class GraphView : VisualElement { - protected readonly ClusterPrefab prefab; + //protected readonly ClusterPrefab prefab; + protected Cluster currentCluster; protected SerializedObject serializedBrain; protected Nucleus currentNucleus; protected Nucleus selectedOutput; protected GameObject gameObject; - private List layers = new(); - private readonly Dictionary neuroidPositions = new(); private bool expandArray = false; protected ClusterPrefab prefabAsset; @@ -34,8 +33,8 @@ namespace NanoBrain { } public Mode mode = Mode.Focus; - public GraphView(ClusterPrefab prefab) { - this.prefab = prefab; + public GraphView(Cluster cluster) { + this.currentCluster = cluster; name = "content"; style.flexGrow = 1; @@ -52,15 +51,6 @@ namespace NanoBrain { modePopup.RegisterValueChangedCallback(OnModeChange); topMenuContainer.Add(modePopup); - // List names = this.prefab.outputs.Select(output => output.name).ToList(); - // if (names.Count > 0 && names.First() != null) { - // outputsPopup = new(names, names.First()) { - // style = { flexGrow = 1 } - // }; - // outputsPopup.RegisterValueChangedCallback(evt => OnOutputChanged(evt.newValue)); - // topMenuContainer.Add(outputsPopup); - // } - scrollView = new(ScrollViewMode.Horizontal); scrollView.style.position = Position.Absolute; scrollView.style.left = 0; scrollView.style.top = 0; @@ -93,16 +83,6 @@ namespace NanoBrain { this.mode = (Mode)changeEvent.newValue; } - // protected virtual void OnOutputChanged(string outputName) { - // if (this.currentNucleus.parent != null) - // // Get nucleus in the parent instance - // this.selectedOutput = this.currentNucleus.parent.GetNucleus(outputName); - // else - // // Get nucleus in the prefab - // this.selectedOutput = this.prefab.GetNucleus(outputName); - // this.currentNucleus = this.selectedOutput; - // } - bool subscribed = false; void Subscribe() { if (subscribed) return; @@ -117,25 +97,21 @@ namespace NanoBrain { subscribed = false; } - public void SetGraph(GameObject gameObject, Nucleus nucleus) { + public void SetGraph(GameObject gameObject) { this.gameObject = gameObject; if (Application.isPlaying == false) - this.serializedBrain = new SerializedObject(this.prefab); - this.currentNucleus = nucleus; - Rebuild(); //inspectorContainer); - this.selectedOutput = this.prefab.outputs[0]; - // if (outputsPopup != null) - // OnOutputChanged(outputsPopup.choices[0]); + this.serializedBrain = new SerializedObject(this.currentCluster.prefab); + this.selectedOutput = this.currentCluster.outputs[0]; + this.currentNucleus = this.selectedOutput; + Rebuild(); } void Rebuild() { - BuildLayers(); - if (this.currentNucleus == null) return; - string path = AssetDatabase.GetAssetPath(this.prefab); // or known path + string path = AssetDatabase.GetAssetPath(this.currentCluster.prefab); // or known path this.prefabAsset = AssetDatabase.LoadAssetAtPath(path); if (this.prefabAsset == null) { // create in memory save if it doesn't exist @@ -442,15 +418,36 @@ namespace NanoBrain { } protected void DrawOutputs(Vector2 parentPos, float size) { - int outputCount = this.prefab.outputs.Count; + // Determine the maximum value in this layer + // This is used to 'scale' the output value colors of the nuclei + float maxValue = 0; + int neuronCount = 0; + List drawnNuclei = new(); + foreach (Nucleus nucleus in this.currentCluster.outputs) { + if (nucleus is not Neuron neuron) + continue; + + // Draw multiple synapses to the same neuron only once + if (drawnNuclei.Contains(nucleus)) + continue; + drawnNuclei.Add(nucleus); + + float value = neuron.outputMagnitude; + if (value > maxValue) + maxValue = value; + + neuronCount++; + } // Determine the spacing of the nuclei in the layer - float spacing = 400f / outputCount; + float spacing = 400f / neuronCount; float margin = 10 + spacing / 2; int row = 0; - List drawnNuclei = new(); - foreach (Nucleus nucleus in this.prefab.outputs) { + drawnNuclei = new(); + foreach (Nucleus nucleus in this.currentCluster.outputs) { + if (nucleus is not Neuron neuron) + continue; // Draw multiple synapses to the same neuron only once if (drawnNuclei.Contains(nucleus)) @@ -459,97 +456,18 @@ namespace NanoBrain { Vector3 pos = new(250, margin + row * spacing, 0.0f); DrawEdge(parentPos, pos); - // Handles.color = Color.white; - // Handles.DrawLine(parentPos, pos); Color color = Color.black; + if (Application.isPlaying) { + if (maxValue == 0 || !float.IsFinite(maxValue)) + maxValue = 1; + float brightness = neuron.outputMagnitude / maxValue; + color = new Color(brightness, brightness, brightness, 1f); + } DrawNucleus(nucleus, pos, size, color); row++; } } - - protected void BuildLayers() { - // A temporary list to track what's been added to layers - this.layers = new(); - int layerIx = 0; - - if (this.currentNucleus == null) { - BuildAllOutputs(); - return; - } - - // Nucleus selectedNucleus = this.currentNucleus; - // if (selectedNucleus == null) - // return; - NeuroidLayer currentLayer = new() { ix = layerIx }; - - // Receivers layer - if (this.currentNucleus is Neuron selectedNeuron && selectedNeuron.receivers != null) { - foreach (Nucleus receiver in selectedNeuron.receivers) { - Nucleus outputNeuroid = receiver; - if (outputNeuroid != null) { - AddToLayer(currentLayer, outputNeuroid); - // Debug.Log($"layer {layerIx} nucleus {outputNeuroid.name}"); - } - } - } - - // Create next layer - if (currentLayer.neuroids.Count > 0) { - this.layers.Add(currentLayer); - layerIx++; - currentLayer = new() { ix = layerIx }; - } - - // Current Nucleus layer - AddToLayer(currentLayer, this.currentNucleus); - this.layers.Add(currentLayer); - // Debug.Log($"layer {layerIx} nucleus {selectedNucleus.name}"); - - // Create next layer - layerIx++; - currentLayer = new() { ix = layerIx }; - - // Synapses layer - if (this.currentNucleus.synapses != null) { - foreach (Synapse synapse in this.currentNucleus.synapses) { - Nucleus input = synapse.neuron; - AddToLayer(currentLayer, input); - // Debug.Log($"layer {layerIx} nucleus {input.name}"); - } - } - if (currentLayer.neuroids.Count > 0) { - this.layers.Add(currentLayer); - } - } - - protected void BuildAllOutputs() { - return; - // Debug.Log("Build all outputs"); - // this.layers = new(); - // int layerIx = 0; - // NeuroidLayer currentLayer = new() { ix = layerIx }; - - // // empty layer, as 'All outputs' is not a nucleus - - // layerIx++; - // currentLayer = new() {ix = layerIx}; - - // foreach (Nucleus nucleus in this.prefab.outputs) { - // AddToLayer(currentLayer, nucleus); - // } - } - - private void AddToLayer(NeuroidLayer layer, Nucleus nucleus) { - if (nucleus == null) - return; - layer.neuroids.Add(nucleus); - // Store its position - Vector2Int neuroidPosition = new(layer.ix, layer.neuroids.Count - 1); - neuroidPositions[nucleus] = neuroidPosition; - } - - #endregion Focus Graph protected void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size) { @@ -593,7 +511,7 @@ namespace NanoBrain { fontStyle = FontStyle.Bold, }; - if (nucleus.parent is Cluster parentCluster && parentCluster != currentNucleus.parent) + if (nucleus.parent is Cluster parentCluster && currentNucleus != null && parentCluster != currentNucleus.parent) DrawCluster(parentCluster, position, color, size); else if (nucleus is Cluster cluster) DrawCluster(cluster, position, color, size); @@ -603,7 +521,7 @@ namespace NanoBrain { Vector3 labelPos = position - Vector3.down * (size + 5); // below neuron style.alignment = TextAnchor.UpperCenter; - if (nucleus.parent != null && nucleus.parent != currentNucleus.parent && nucleus.parent is Cluster parentCluster1) { + if (nucleus.parent != null && currentNucleus != null && nucleus.parent != currentNucleus.parent && nucleus.parent is Cluster parentCluster1) { // This neuron is part of another cluster parentCluster1.name ??= ""; string baseName = ""; @@ -764,7 +682,7 @@ namespace NanoBrain { if (len <= 2f * radius || len <= Mathf.Epsilon) // line too short return; - + Vector2 n = dir / len; // normalized Vector2 a = from + n * radius; Vector2 b = to - n * radius; @@ -803,19 +721,17 @@ namespace NanoBrain { OnClusterClick(cluster); } } - else if (nucleus.parent != null && nucleus.parent != this.currentNucleus.parent) { + else if (nucleus.parent != null && this.currentNucleus != null && nucleus.parent != this.currentNucleus.parent) { // We go to a different cluster // select the cluster, not the neuron in the cluster this.currentNucleus = nucleus.parent; expandArray = false; - BuildLayers(); } else { this.currentNucleus = nucleus; if (this.currentNucleus is Neuron neuron && neuron.receivers.Count == 0) this.selectedOutput = this.currentNucleus; expandArray = false; - BuildLayers(); } } @@ -823,7 +739,7 @@ namespace NanoBrain { // May be used with storedPrefab... Selection.activeObject = subCluster.prefab; EditorGUIUtility.PingObject(subCluster.prefab); - ClusterViewer.previousPrefab = this.prefab; + ClusterViewer.previousPrefab = this.currentCluster.prefab; ClusterEditor newEditor = CreateEditor(subCluster.prefab) as ClusterEditor; } @@ -831,7 +747,6 @@ namespace NanoBrain { this.currentNucleus = null; this.selectedOutput = null; expandArray = false; - BuildLayers(); } #endregion Graph diff --git a/Runtime/Scripts/Brain.cs b/Runtime/Scripts/Brain.cs index 36fd6e7..acb28f3 100644 --- a/Runtime/Scripts/Brain.cs +++ b/Runtime/Scripts/Brain.cs @@ -20,23 +20,23 @@ namespace NanoBrain { /// public Cluster brain { get { - // if (brainInstance == null && brainPrefab != null) { - // brainInstance = new Cluster(brainPrefab) { - // name = brainPrefab.name - // }; - // } else if (brainInstance != null && brainPrefab == null) { - // brainInstance = null; - // } + if (brainInstance == null && brainPrefab != null) { + brainInstance = new Cluster(brainPrefab) { + name = brainPrefab.name + }; + } else if (brainInstance != null && brainPrefab == null) { + brainInstance = null; + } return brainInstance; } } - public Cluster InitializeBrain() { - brainInstance = new Cluster(brainPrefab) { - name = brainPrefab.name - }; - return brainInstance; - } + // public Cluster InitializeBrain() { + // brainInstance = new Cluster(brainPrefab) { + // name = brainPrefab.name + // }; + // return brainInstance; + // } /// /// Update the weight for all Synapses coming from the Neuron with the given name diff --git a/Runtime/Scripts/Core/Cluster.cs b/Runtime/Scripts/Core/Cluster.cs index 525e296..0b89721 100644 --- a/Runtime/Scripts/Core/Cluster.cs +++ b/Runtime/Scripts/Core/Cluster.cs @@ -636,7 +636,7 @@ namespace NanoBrain { if (this._outputs == null) { this._outputs = new(); foreach (Nucleus nucleus in this.clusterNuclei) { - if (nucleus is Neuron neuron) // && neuron.receivers.Count == 0) + if (nucleus is Neuron neuron && neuron.receivers.Count == 0) this._outputs.Add(neuron); } }