diff --git a/Assembly-CSharp-Editor.csproj b/Assembly-CSharp-Editor.csproj index 369d5d7..94f9e7e 100644 --- a/Assembly-CSharp-Editor.csproj +++ b/Assembly-CSharp-Editor.csproj @@ -51,8 +51,10 @@ + + diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj index 49d2ce0..b49e8b7 100644 --- a/Assembly-CSharp.csproj +++ b/Assembly-CSharp.csproj @@ -71,12 +71,14 @@ + + diff --git a/Assets/NanoBrain/Cluster.cs b/Assets/NanoBrain/Cluster.cs new file mode 100644 index 0000000..991c44e --- /dev/null +++ b/Assets/NanoBrain/Cluster.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using UnityEngine; + +[CreateAssetMenu(menuName = "Passer/Cluster")] +public class Cluster : ScriptableObject, INucleus { + + private string _name; + public string name { + get { return _name; } + set { _name = value; } + } + + public Cluster cluster => this; + + public List nuclei = new(); + + public Nucleus output => this.nuclei[0]; + + private readonly List _synapses = new(); // = inputs, compare receptors in NanoBrain + public List synapses => _synapses; + + private void OnEnable() { + nuclei ??= new List(); + if (nuclei.Count == 0) + new Neuroid(this, "Output"); // Every cluster should have at least 1 neuroid + } + + public void AddReceiver(INucleus receiver) { + output.AddReceiver(receiver); + } + + public void RemoveReceiver(INucleus receiver) { + output.RemoveReceiver(receiver); + } + public List receivers { + get => output.receivers; + } + + public void GarbageCollection() { + HashSet visitedNuclei = new(); + MarkNuclei(visitedNuclei, this.output); + //Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei"); + this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false); + //this.perceptei.RemoveAll(perceptoid => visitedNuclei.Contains(perceptoid) == false); + } + + public void MarkNuclei(HashSet visitedNuclei, INucleus nucleus) { + if (nucleus is null) + return; + + visitedNuclei.Add(nucleus); + if (nucleus.synapses != null) { + HashSet visitedSynapses = new(); + foreach (Synapse synapse in nucleus.synapses) { + if (synapse != null && synapse.nucleus != null) { + visitedSynapses.Add(synapse); + MarkNuclei(visitedNuclei, synapse.nucleus); + } + } + nucleus.synapses.RemoveAll(synapse => visitedSynapses.Contains(synapse) == false); + } + if (nucleus.receivers != null) { + HashSet visitedReceivers = new(); + foreach (Receiver receiver in nucleus.receivers) { + if (receiver != null && receiver.nucleus != null) { + visitedReceivers.Add(receiver); + visitedNuclei.Add(receiver.nucleus); + } + } + nucleus.receivers.RemoveAll(receiver => visitedReceivers.Contains(receiver) == false); + } + } + + #region Dynamics + + public Vector3 outputValue => this.output.outputValue; + public bool isSleeping => this.outputValue.sqrMagnitude == 0; + + public void UpdateState() { + // Don't know if this is right + this.output.UpdateState(); + } + + #endregion Dynamics +} \ No newline at end of file diff --git a/Assets/NanoBrain/Cluster.cs.meta b/Assets/NanoBrain/Cluster.cs.meta new file mode 100644 index 0000000..ee35e0b --- /dev/null +++ b/Assets/NanoBrain/Cluster.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 60a957541c24c57e78018c202ebb1d9b \ No newline at end of file diff --git a/Assets/NanoBrain/Editor/NeuroidWindow.cs b/Assets/NanoBrain/Editor/NeuroidWindow.cs index 72494b8..187df35 100644 --- a/Assets/NanoBrain/Editor/NeuroidWindow.cs +++ b/Assets/NanoBrain/Editor/NeuroidWindow.cs @@ -1,300 +1,302 @@ -using UnityEditor; -using UnityEngine; -using System.Linq; -using System.Collections.Generic; - -public class NeuroidLayer { - public int ix = 0; - public List neuroids = new(); -} - -public class GraphEditorWindow : EditorWindow { - private Nucleus currentNucleus; - private List allNeuroids; - private Dictionary neuroidPositions = new(); - - private List layers = new(); - - private void OnEnable() { - EditorApplication.update += EditorUpdate; - Selection.selectionChanged += OnSelectionChange; - SelectNeuron(); - } - - private void AddToLayer(NeuroidLayer layer, Nucleus nucleus) { - layer.neuroids.Add(nucleus); - nucleus.layerIx = layer.ix; - // Store its position - Vector2Int neuroidPosition = new(layer.ix, layer.neuroids.Count - 1); - neuroidPositions[nucleus] = neuroidPosition; - - } - - 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 }; - - //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}"); - } - } - 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 }; - - int six = 0; - // foreach (Synapse synapse in selectedNucleus.synapses.Values) { - // 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 (Synapse synapse in selectedNucleus.synapses) { - Nucleus input = synapse.nucleus; - if (input != null) { - AddToLayer(currentLayer, input); - Debug.Log($"layer {layerIx} nucleus {input.name}"); - } - six++; - } - if (currentLayer.neuroids.Count > 0) { - this.layers.Add(currentLayer); - } - } - - private void BuildLayers_old(List neuroids) { - if (neuroids == null) - return; - - // A temporary list to track what's been added to layers - this.layers = new(); - HashSet neuronVisited = new(); - int layerIx = 0; - - // While there are unvisited neuroid - while (neuroids.Any(neuroid => !neuronVisited.Contains(neuroid))) { - // Create the next layer - NeuroidLayer currentLayer = new() { ix = layerIx }; - int neuroidIx = 0; - - foreach (Neuroid neuroid in neuroids) { - // Skip neurons we already processed - if (neuronVisited.Contains(neuroid)) - continue; - - // if (neuroid.IsStale()) { - // Debug.Log($"neuron {neuroid.name} is stale {neuroid.stale}"); - // neuronVisited.Add(neuroid); - // continue; - // } - - // 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().nucleus) && neuroid.receivers.First().nucleus.layerIx == layerIx - 1)) { - // Add it to the next layer - currentLayer.neuroids.Add(neuroid); - neuroid.layerIx = layerIx; - // Register it as visited - neuronVisited.Add(neuroid); - // Store its position - Vector2Int neuroidPosition = new(layerIx, neuroidIx); - neuroidPositions[neuroid] = neuroidPosition; - neuroidIx++; - Debug.Log($"Layer {layerIx} neuron {neuroidIx} name {neuroid.name}"); - } - } - - if (currentLayer.neuroids.Count > 0) { - this.layers.Add(currentLayer); - layerIx++; - } - } - } - - private void OnDisable() { - EditorApplication.update -= EditorUpdate; - Selection.selectionChanged -= OnSelectionChange; - } - - private void OnSelectionChange() { - SelectNeuron(); - Repaint(); - } - - private void EditorUpdate() { - if (EditorApplication.isPlaying) - Repaint(); - } - - private void OnGUI() { - GUILayout.Label("Graph Visualizer", EditorStyles.boldLabel); - - DrawGraph(); - } - - private void DrawGraph() { - if (currentNucleus == null) - return; - - foreach (NeuroidLayer layer in layers) - DrawLayer(layer); - } - - private void DrawLayer(NeuroidLayer layer) { - int column = layer.ix * 100; - 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 = 100 + spacing / 2; - foreach (Nucleus layerNucleus in layer.neuroids) { - if (layerNucleus is Neuroid layerNeuroid) { - Vector2Int layerNeuroidPos = this.neuroidPositions[layerNeuroid]; - Vector3 parentPos = new(100 + layerNeuroidPos.x * 100, margin + layerNeuroidPos.y * spacing, 0.1f); - - int i = 0; - float inputSpacing = 400f / layerNeuroid.synapses.Count; - float inputMargin = 100 + inputSpacing / 2; - // foreach (Synapse synapse in layerNeuroid.synapses.Values) { - // if (synapse.neuroid != null) { - // if (this.neuroidPositions.ContainsKey(synapse.neuroid)) { - - // Vector2Int inputNeuroidPos = this.neuroidPositions[synapse.neuroid]; - //foreach ((Nucleus neuroid, Synapse synapse) 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]; - if (inputNeuroidPos.x == layerNeuroidPos.x + 1) { - Vector3 pos = new(100 + inputNeuroidPos.x * 100, inputMargin + inputNeuroidPos.y * inputSpacing, 0.0f); - - //float brightness = synapse.weight / 10.0f; - float brightness = weight / 10.0f; - Handles.color = new Color(brightness, brightness, brightness); - Handles.DrawLine(parentPos, pos); - } - } - } - } - - float size = 20; - if (layerNeuroid.isSleeping) - Handles.color = Color.black; - else { - float brightness = layerNeuroid.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, layerNeuroid.name, style); - - Rect neuronRect = new(parentPos.x - size, parentPos.y - size, size * 2, size * 2); - Event e = Event.current; - if (e != null && neuronRect.Contains(e.mousePosition)) { - HandleMouseHover(layerNeuroid, neuronRect); - // Process click - if (e.type == EventType.MouseDown && e.button == 0) { - // Consume the event so the scene doesn't also handle it - e.Use(); - HandleDiscClicked(layerNeuroid); - } - } - i++; - } - } - } - - private void HandleMouseHover(Neuroid neuroid, Rect rect) { - GUIContent tooltip; - // if (neuroid is SensoryNeuroid sensoryNeuroid) { - // tooltip = new( - // $"{sensoryNeuroid.name}" + - // $"\nThing {sensoryNeuroid.receptor.thingType}" + - // $"\nValue: {neuroid.outputValue}"); - // } - // else { - tooltip = new( - $"{neuroid.name}" + - $"\nsynapse count {neuroid.synapses.Count}" + - $"\nValue: {neuroid.outputValue}"); - // } - - 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 HandleDiscClicked(Nucleus nucleus) { - this.currentNucleus = nucleus; - BuildLayers(); - } - - // Update node colors based on selected GameObjects - private void SelectNeuron() { - GameObject[] selectedObjects = Selection.gameObjects; - if (selectedObjects.Length == 0) - return; - - GameObject selectedObject = selectedObjects[0]; - Boid boid = selectedObject.GetComponent(); - if (boid == null) - return; - - // 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}"); - - } - - [MenuItem("Window/Neuroid Visualizer")] - public static void ShowWindow() { - GetWindow("Neuroid Visualizer"); - } -} +/* +using UnityEditor; +using UnityEngine; +using System.Linq; +using System.Collections.Generic; + +public class NeuroidLayer { + public int ix = 0; + public List neuroids = new(); +} + +public class GraphEditorWindow : EditorWindow { + private Nucleus currentNucleus; + private List allNeuroids; + private Dictionary neuroidPositions = new(); + + private List layers = new(); + + private void OnEnable() { + EditorApplication.update += EditorUpdate; + Selection.selectionChanged += OnSelectionChange; + SelectNeuron(); + } + + private void AddToLayer(NeuroidLayer layer, Nucleus nucleus) { + layer.neuroids.Add(nucleus); + nucleus.layerIx = layer.ix; + // Store its position + Vector2Int neuroidPosition = new(layer.ix, layer.neuroids.Count - 1); + neuroidPositions[nucleus] = neuroidPosition; + + } + + 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 }; + + //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}"); + } + } + 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 }; + + int six = 0; + // foreach (Synapse synapse in selectedNucleus.synapses.Values) { + // 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 (Synapse synapse in selectedNucleus.synapses) { + Nucleus input = synapse.nucleus; + if (input != null) { + AddToLayer(currentLayer, input); + Debug.Log($"layer {layerIx} nucleus {input.name}"); + } + six++; + } + if (currentLayer.neuroids.Count > 0) { + this.layers.Add(currentLayer); + } + } + + private void BuildLayers_old(List neuroids) { + if (neuroids == null) + return; + + // A temporary list to track what's been added to layers + this.layers = new(); + HashSet neuronVisited = new(); + int layerIx = 0; + + // While there are unvisited neuroid + while (neuroids.Any(neuroid => !neuronVisited.Contains(neuroid))) { + // Create the next layer + NeuroidLayer currentLayer = new() { ix = layerIx }; + int neuroidIx = 0; + + foreach (Neuroid neuroid in neuroids) { + // Skip neurons we already processed + if (neuronVisited.Contains(neuroid)) + continue; + + // if (neuroid.IsStale()) { + // Debug.Log($"neuron {neuroid.name} is stale {neuroid.stale}"); + // neuronVisited.Add(neuroid); + // continue; + // } + + // 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().nucleus) && neuroid.receivers.First().nucleus.layerIx == layerIx - 1)) { + // Add it to the next layer + currentLayer.neuroids.Add(neuroid); + neuroid.layerIx = layerIx; + // Register it as visited + neuronVisited.Add(neuroid); + // Store its position + Vector2Int neuroidPosition = new(layerIx, neuroidIx); + neuroidPositions[neuroid] = neuroidPosition; + neuroidIx++; + Debug.Log($"Layer {layerIx} neuron {neuroidIx} name {neuroid.name}"); + } + } + + if (currentLayer.neuroids.Count > 0) { + this.layers.Add(currentLayer); + layerIx++; + } + } + } + + private void OnDisable() { + EditorApplication.update -= EditorUpdate; + Selection.selectionChanged -= OnSelectionChange; + } + + private void OnSelectionChange() { + SelectNeuron(); + Repaint(); + } + + private void EditorUpdate() { + if (EditorApplication.isPlaying) + Repaint(); + } + + private void OnGUI() { + GUILayout.Label("Graph Visualizer", EditorStyles.boldLabel); + + DrawGraph(); + } + + private void DrawGraph() { + if (currentNucleus == null) + return; + + foreach (NeuroidLayer layer in layers) + DrawLayer(layer); + } + + private void DrawLayer(NeuroidLayer layer) { + int column = layer.ix * 100; + 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 = 100 + spacing / 2; + foreach (Nucleus layerNucleus in layer.neuroids) { + if (layerNucleus is Neuroid layerNeuroid) { + Vector2Int layerNeuroidPos = this.neuroidPositions[layerNeuroid]; + Vector3 parentPos = new(100 + layerNeuroidPos.x * 100, margin + layerNeuroidPos.y * spacing, 0.1f); + + int i = 0; + float inputSpacing = 400f / layerNeuroid.synapses.Count; + float inputMargin = 100 + inputSpacing / 2; + // foreach (Synapse synapse in layerNeuroid.synapses.Values) { + // if (synapse.neuroid != null) { + // if (this.neuroidPositions.ContainsKey(synapse.neuroid)) { + + // Vector2Int inputNeuroidPos = this.neuroidPositions[synapse.neuroid]; + //foreach ((Nucleus neuroid, Synapse synapse) 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]; + if (inputNeuroidPos.x == layerNeuroidPos.x + 1) { + Vector3 pos = new(100 + inputNeuroidPos.x * 100, inputMargin + inputNeuroidPos.y * inputSpacing, 0.0f); + + //float brightness = synapse.weight / 10.0f; + float brightness = weight / 10.0f; + Handles.color = new Color(brightness, brightness, brightness); + Handles.DrawLine(parentPos, pos); + } + } + } + } + + float size = 20; + if (layerNeuroid.isSleeping) + Handles.color = Color.black; + else { + float brightness = layerNeuroid.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, layerNeuroid.name, style); + + Rect neuronRect = new(parentPos.x - size, parentPos.y - size, size * 2, size * 2); + Event e = Event.current; + if (e != null && neuronRect.Contains(e.mousePosition)) { + HandleMouseHover(layerNeuroid, neuronRect); + // Process click + if (e.type == EventType.MouseDown && e.button == 0) { + // Consume the event so the scene doesn't also handle it + e.Use(); + HandleDiscClicked(layerNeuroid); + } + } + i++; + } + } + } + + private void HandleMouseHover(Neuroid neuroid, Rect rect) { + GUIContent tooltip; + // if (neuroid is SensoryNeuroid sensoryNeuroid) { + // tooltip = new( + // $"{sensoryNeuroid.name}" + + // $"\nThing {sensoryNeuroid.receptor.thingType}" + + // $"\nValue: {neuroid.outputValue}"); + // } + // else { + tooltip = new( + $"{neuroid.name}" + + $"\nsynapse count {neuroid.synapses.Count}" + + $"\nValue: {neuroid.outputValue}"); + // } + + 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 HandleDiscClicked(Nucleus nucleus) { + this.currentNucleus = nucleus; + BuildLayers(); + } + + // Update node colors based on selected GameObjects + private void SelectNeuron() { + GameObject[] selectedObjects = Selection.gameObjects; + if (selectedObjects.Length == 0) + return; + + GameObject selectedObject = selectedObjects[0]; + Boid boid = selectedObject.GetComponent(); + if (boid == null) + return; + + // 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}"); + + } + + [MenuItem("Window/Neuroid Visualizer")] + public static void ShowWindow() { + GetWindow("Neuroid Visualizer"); + } +} +*/ \ No newline at end of file diff --git a/Assets/NanoBrain/INucleus.cs b/Assets/NanoBrain/INucleus.cs new file mode 100644 index 0000000..757f8ef --- /dev/null +++ b/Assets/NanoBrain/INucleus.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using UnityEngine; + +public interface INucleus { + + #region static struct + + public string name { get; set; } + + // Cluster + public Cluster cluster { get; } + + // Receivers + public List receivers { get; } + public void AddReceiver(INucleus receiver); + public void RemoveReceiver(INucleus receiverNucleus); + + // Senders + public List synapses { get; } + + #endregion static struct + + #region dynamic state + + public bool isSleeping { get; } + + public Vector3 outputValue { get; } + + public void UpdateState(); + + #endregion dynamic state +} \ No newline at end of file diff --git a/Assets/NanoBrain/INucleus.cs.meta b/Assets/NanoBrain/INucleus.cs.meta new file mode 100644 index 0000000..aed95bb --- /dev/null +++ b/Assets/NanoBrain/INucleus.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6a8a0e8965cea660abff254cab8a4723 \ No newline at end of file diff --git a/Assets/NanoBrain/Neuroid.cs b/Assets/NanoBrain/Neuroid.cs index 0f9894a..cd534e0 100644 --- a/Assets/NanoBrain/Neuroid.cs +++ b/Assets/NanoBrain/Neuroid.cs @@ -1,112 +1,112 @@ -using UnityEngine; - -[System.Serializable] -public class Neuroid : Nucleus { - public enum CurvePresets { - Linear, - Power, - Sqrt, - Reciprocal, - Custom - } - [SerializeField] - private CurvePresets _curvePreset; - public CurvePresets curvePreset { - get { return _curvePreset; } - set { - _curvePreset = value; - this.curve = GenerateCurve(); - } - } - public AnimationCurve curve; - public float curveMax = 1.0f; - - public AnimationCurve GenerateCurve() { - switch (this.curvePreset) { - case CurvePresets.Linear: - this.curveMax = 1; - return Synapse.Presets.Linear(1); - case CurvePresets.Power: - this.curveMax = 1; - return Synapse.Presets.Power(2.0f, 1); - case CurvePresets.Sqrt: - this.curveMax = 1; - return Synapse.Presets.Power(0.5f, 1); - case CurvePresets.Reciprocal: - this.curveMax = 1 / 0.01f * 1; - return Synapse.Presets.Reciprocal(1); - default: - this.curveMax = 1; - return this.curve; - } - } - - public bool average = false; - public bool inverse = false; - public float exponent = 1.0f; - - - public Neuroid(NanoBrain brain, string name) : base(name) { - this.brain = brain; - if (this.brain != null) { - this.brain.nuclei.Add(this); - } - else - Debug.LogError("No neuroid network"); - } - - public Neuroid(string name) : base(name) { } - - public void SetWeight(Neuroid input, float weight) { - this.SetWeight((Nucleus)input, weight); - } - - public void SetInput(Neuroid input) { - if (this.SynapseExists(input) == false) - this.SetWeight(input, 1.0f); - UpdateState(); - } - - public void SetInput(Neuroid input, float weight) { - this.SetWeight(input, weight); - UpdateState(); - } - - public override void UpdateState() { - Vector3 sum = Vector3.zero; - int n = 0; - - //Applying the weight factgors - foreach (Synapse synapse in this.synapses) { - sum += synapse.weight * synapse.nucleus.outputValue; - if (synapse.nucleus.outputValue.sqrMagnitude != 0) - n++; - } - if (average) - sum /= n; - - // Activation function - Vector3 result; - switch (this.curvePreset) { - case CurvePresets.Linear: - result = sum; - break; - case CurvePresets.Sqrt: - result = sum.normalized * System.MathF.Sqrt(sum.magnitude); - break; - case CurvePresets.Power: - result = sum.normalized * System.MathF.Pow(sum.magnitude, 2); - break; - case CurvePresets.Reciprocal: - result = sum.normalized * (1 / sum.magnitude); - break; - default: - float activatedValue = this.curve.Evaluate(sum.magnitude); - result = sum.normalized * activatedValue; - break; - } - UpdateResult(result); - } - -} - +using UnityEngine; + +[System.Serializable] +public class Neuroid : Nucleus { + public enum CurvePresets { + Linear, + Power, + Sqrt, + Reciprocal, + Custom + } + [SerializeField] + private CurvePresets _curvePreset; + public CurvePresets curvePreset { + get { return _curvePreset; } + set { + _curvePreset = value; + this.curve = GenerateCurve(); + } + } + public AnimationCurve curve; + public float curveMax = 1.0f; + + public AnimationCurve GenerateCurve() { + switch (this.curvePreset) { + case CurvePresets.Linear: + this.curveMax = 1; + return Synapse.Presets.Linear(1); + case CurvePresets.Power: + this.curveMax = 1; + return Synapse.Presets.Power(2.0f, 1); + case CurvePresets.Sqrt: + this.curveMax = 1; + return Synapse.Presets.Power(0.5f, 1); + case CurvePresets.Reciprocal: + this.curveMax = 1 / 0.01f * 1; + return Synapse.Presets.Reciprocal(1); + default: + this.curveMax = 1; + return this.curve; + } + } + + public bool average = false; + public bool inverse = false; + public float exponent = 1.0f; + + + public Neuroid(Cluster brain, string name) : base(name) { + this.cluster = brain; + if (this.cluster != null) { + this.cluster.nuclei.Add(this); + } + else + Debug.LogError("No neuroid network"); + } + + public Neuroid(string name) : base(name) { } + + public void SetWeight(Neuroid input, float weight) { + this.SetWeight((Nucleus)input, weight); + } + + public void SetInput(Neuroid input) { + if (this.SynapseExists(input) == false) + this.SetWeight(input, 1.0f); + UpdateState(); + } + + public void SetInput(Neuroid input, float weight) { + this.SetWeight(input, weight); + UpdateState(); + } + + public override void UpdateState() { + Vector3 sum = Vector3.zero; + int n = 0; + + //Applying the weight factgors + foreach (Synapse synapse in this.synapses) { + sum += synapse.weight * synapse.nucleus.outputValue; + if (synapse.nucleus.outputValue.sqrMagnitude != 0) + n++; + } + if (average) + sum /= n; + + // Activation function + Vector3 result; + switch (this.curvePreset) { + case CurvePresets.Linear: + result = sum; + break; + case CurvePresets.Sqrt: + result = sum.normalized * System.MathF.Sqrt(sum.magnitude); + break; + case CurvePresets.Power: + result = sum.normalized * System.MathF.Pow(sum.magnitude, 2); + break; + case CurvePresets.Reciprocal: + result = sum.normalized * (1 / sum.magnitude); + break; + default: + float activatedValue = this.curve.Evaluate(sum.magnitude); + result = sum.normalized * activatedValue; + break; + } + UpdateResult(result); + } + +} + diff --git a/Assets/NanoBrain/Nucleus.cs b/Assets/NanoBrain/Nucleus.cs index 62e4481..79c05cb 100644 --- a/Assets/NanoBrain/Nucleus.cs +++ b/Assets/NanoBrain/Nucleus.cs @@ -1,304 +1,306 @@ -using System; -using System.Collections.Generic; -using UnityEngine; -using UnityEditor; -using LinearAlgebra; - -[System.Serializable] -public class Nucleus { - - public int id; // hash code - - [SerializeField] - protected string _name; - public virtual string name { - get => _name; - set => _name = value; - } - - [SerializeField] - public List synapses = new(); - [SerializeField] - public List receivers = new(); - - #region Serialization - - [SerializeField] - protected string nucleusType; - - public virtual void Rebuild(NanoBrain 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.ToArray()) { - if (receiver.Rebuild(brain) == false) { - Debug.Log("Rebuilding failed, removing receiver."); - receivers.Remove(receiver); - } - } - } - } - - public static Nucleus RebuildType(NanoBrain brain, Nucleus nucleus) { - if (string.IsNullOrEmpty(nucleus.nucleusType) == false) { - Type nucleusType = Type.GetType(nucleus.nucleusType); - if (nucleusType != null) { - object[] args = new object[] { brain, nucleus.name }; - Nucleus rebuiltNucleus = (Nucleus)Activator.CreateInstance(nucleusType, args); - rebuiltNucleus.Deserialize(nucleus); - return rebuiltNucleus; - } - } - return nucleus; - } - - public virtual void Deserialize(Nucleus nucleus) { } - - #endregion Serialization - - #region Runtime state (not serialized) - - public NanoBrain brain { get; set; } - - private Vector3 _outputValue; - public Vector3 outputValue - { - get { return _outputValue; } - set { - this.stale = 0; - this.isSleeping = false; - _outputValue = value; - } - } - - [System.NonSerialized] - private int stale = 1000; - - public bool isSleeping = false; - public void IncreaseAge() { - this.stale++; - this.isSleeping = this.stale > 2; - if (isSleeping) - _outputValue = Vector3.zero; - } - [System.NonSerialized] - public int layerIx; - - #endregion Runtime state - - public Nucleus(string name) { - this._name = name; - this.id = this.GetHashCode(); - } - - public virtual void AddReceiver(Nucleus receiver) { - this.receivers.Add(new Receiver(receiver)); - receiver.SetWeight(this, 1.0f); - } - - public void RemoveReceiver(Nucleus receiverNucleus) { - this.receivers.RemoveAll(receiver => receiver.nucleus == receiverNucleus); - receiverNucleus.synapses.RemoveAll(synapse => synapse.nucleus == this); - } - - 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) { - if (receiver.nucleus != null && receiver.nucleus.synapses != null) - receiver.nucleus.synapses.RemoveAll(s => s.nucleus == nucleus); - } - - if (nucleus.brain != null) { - nucleus.brain.nuclei.RemoveAll(n => n == nucleus); - nucleus.brain.GarbageCollection(); - } - } - - public void GetInputFrom(Nucleus input, float weight = 1.0f) { - input.AddReceiver(this); - this.SetWeight(input, weight); - } - - 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); - } - - public virtual void UpdateState() { } - - public void UpdateResult(Vector3 result) { - // float d = Vector3.Distance(result, this.outputValue); - // if (d < 0.5f) { - // //Debug.Log($"insignificant update: {d}"); - // return; - // } - - this.outputValue = result; - foreach (Receiver receiver in this.receivers) - receiver.nucleus.UpdateState(); - - } -} - -[System.Serializable] -public class Synapse { - [System.NonSerialized] - public Nucleus nucleus; - public int nucleusId; - public float weight; - - public enum CurvePresets { - Linear, - Power, - Sqrt, - Reciprocal, - Custom - } - // public CurvePresets curvePreset; - // public AnimationCurve curve; - public float curveMax = 1.0f; - - public Synapse(Nucleus nucleus, float weight) { - this.nucleus = nucleus; - this.nucleusId = nucleus.id; - this.weight = weight; - } - - public void Rebuild(NanoBrain brain) { - if (brain == null) { - return; - } - - foreach (Nucleus nucleus in brain.nuclei) { - if (nucleus.id == this.nucleusId) { - this.nucleus = nucleus; - return; - } - } - foreach (Perceptoid perceptoid in brain.perceptei) { - if (perceptoid.id == this.nucleusId) { - this.nucleus = perceptoid; - return; - } - } - Debug.LogError($"Synapse deserialization error: could not find nucleus with id {this.nucleusId}"); - } - - // public AnimationCurve GenerateCurve() { - // switch (this.curvePreset) { - // case CurvePresets.Linear: - // this.curveMax = this.weight; - // return Presets.Linear(this.weight); - // case CurvePresets.Power: - // this.curveMax = this.weight; - // return Presets.Power(2.0f, this.weight); - // case CurvePresets.Sqrt: - // this.curveMax = this.weight; - // return Presets.Power(0.5f, this.weight); - // case CurvePresets.Reciprocal: - // this.curveMax = 1 / 0.01f * this.weight; - // return Presets.Reciprocal(this.weight); - // default: - // this.curveMax = weight; - // return AnimationCurve.Constant(0, 1, weight); - // } - // } - - public static class Presets { - private const int samples = 32; - public static AnimationCurve Linear(float weight) { - return AnimationCurve.Linear(0f, 0f, 1000f, weight * 1000); - } - public static AnimationCurve Power(float exponent, float weight) { - // build keyframes - Keyframe[] keys = new Keyframe[samples]; - for (int i = 0; i < samples; i++) { - float t = i / (float)(samples - 1); - float v = Mathf.Pow(t, exponent) * weight; - keys[i] = new Keyframe(t, v); - } - - AnimationCurve curve = new(keys); - - // set tangent modes for each key to Auto (smooth). Use Linear if you prefer straight segments. - for (int i = 0; i < curve.length; i++) { - AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Auto); - AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Auto); - } - - return curve; - } - public static AnimationCurve Reciprocal(float weight) { - int samples = 128; - float xMin = 0.001f; - float xMax = 1; - var keys = new Keyframe[samples]; - for (int i = 0; i < samples; i++) { - float t = i / (float)(samples - 1); - float x = Mathf.Lerp(xMin, xMax, t); - float y = 1f / x * weight; - keys[i] = new Keyframe(x, y); - } - var curve = new AnimationCurve(keys); - for (int i = 0; i < curve.length; i++) { - AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Linear); - AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Linear); - } - return curve; - } - } -} - -[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 bool Rebuild(NanoBrain brain) { - if (brain == null) { - return false; - } - - foreach (Nucleus nucleus in brain.nuclei) { - if (nucleus.id == this.nucleusId) { - this.nucleus = nucleus; - return true; - } - } - Debug.LogWarning($"Receiver deserialization error: could not find nucleus with id {this.nucleusId}"); - return false; - } +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +[Serializable] +public class Nucleus : INucleus { + + public int id; // hash code + + [SerializeField] + protected string _name; + public virtual string name { + get => _name; + set => _name = value; + } + + [SerializeField] + private List _synapses = new(); + public List synapses => _synapses; + + [SerializeField] + private List _receivers = new(); + public List receivers => _receivers; + + #region Serialization + + [SerializeField] + protected string nucleusType; + + public virtual void Rebuild(NanoBrain brain) { + if (this.synapses != null) { + foreach (Synapse synapse in synapses) + synapse.Rebuild(brain); + } + foreach (Receiver receiver in receivers.ToArray()) { + if (receiver.Rebuild(brain) == false) { + Debug.Log("Rebuilding failed, removing receiver."); + receivers.Remove(receiver); + } + } + } + + public static Nucleus RebuildType(NanoBrain brain, Nucleus nucleus) { + if (string.IsNullOrEmpty(nucleus.nucleusType) == false) { + Type nucleusType = Type.GetType(nucleus.nucleusType); + if (nucleusType != null) { + object[] args = new object[] { brain, nucleus.name }; + Nucleus rebuiltNucleus = (Nucleus)Activator.CreateInstance(nucleusType, args); + rebuiltNucleus.Deserialize(nucleus); + return rebuiltNucleus; + } + } + return nucleus; + } + + public virtual void Deserialize(Nucleus nucleus) { } + + #endregion Serialization + + #region Runtime state (not serialized) + + public Cluster cluster { get; set; } + + private Vector3 _outputValue; + public Vector3 outputValue + { + get { return _outputValue; } + set { + this.stale = 0; + this._isSleeping = false; + _outputValue = value; + } + } + + [System.NonSerialized] + private int stale = 1000; + + private bool _isSleeping = false; + public bool isSleeping => _isSleeping; + + public void IncreaseAge() { + this.stale++; + this._isSleeping = this.stale > 2; + if (isSleeping) + _outputValue = Vector3.zero; + } + [System.NonSerialized] + public int layerIx; + + #endregion Runtime state + + public Nucleus(string name) { + this._name = name; + this.id = this.GetHashCode(); + } + + public virtual void AddReceiver(INucleus receiver) { + this.receivers.Add(new Receiver(receiver)); + //receiver.SetWeight(this, 1.0f); + } + + public void RemoveReceiver(INucleus receiverNucleus) { + this.receivers.RemoveAll(receiver => receiver.nucleus == receiverNucleus); + receiverNucleus.synapses.RemoveAll(synapse => synapse.nucleus == this); + } + + public static void Delete(INucleus 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) { + if (receiver.nucleus != null && receiver.nucleus.synapses != null) + receiver.nucleus.synapses.RemoveAll(s => s.nucleus == nucleus); + } + + if (nucleus.cluster != null) { + nucleus.cluster.nuclei.RemoveAll(n => n == nucleus); + nucleus.cluster.GarbageCollection(); + } + } + + public void GetInputFrom(Nucleus input, float weight = 1.0f) { + input.AddReceiver(this); + this.SetWeight(input, weight); + } + + 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); + } + + public virtual void UpdateState() { } + + public void UpdateResult(Vector3 result) { + // float d = Vector3.Distance(result, this.outputValue); + // if (d < 0.5f) { + // //Debug.Log($"insignificant update: {d}"); + // return; + // } + + this.outputValue = result; + foreach (Receiver receiver in this.receivers) + receiver.nucleus.UpdateState(); + + } +} + +[System.Serializable] +public class Synapse { + [System.NonSerialized] + public INucleus nucleus; + public NanoBrain cluster; + public int nucleusId; + public float weight; + + public enum CurvePresets { + Linear, + Power, + Sqrt, + Reciprocal, + Custom + } + // public CurvePresets curvePreset; + // public AnimationCurve curve; + public float curveMax = 1.0f; + + public Synapse(Nucleus nucleus, float weight) { + this.nucleus = nucleus; + this.nucleusId = nucleus.id; + this.weight = weight; + } + + public void Rebuild(NanoBrain brain) { + if (brain == null) { + return; + } + + foreach (Nucleus nucleus in brain.nuclei) { + if (nucleus.id == this.nucleusId) { + this.nucleus = nucleus; + return; + } + } + foreach (Perceptoid perceptoid in brain.perceptei) { + if (perceptoid.id == this.nucleusId) { + this.nucleus = perceptoid; + return; + } + } + Debug.LogError($"Synapse deserialization error: could not find nucleus with id {this.nucleusId}"); + } + + // public AnimationCurve GenerateCurve() { + // switch (this.curvePreset) { + // case CurvePresets.Linear: + // this.curveMax = this.weight; + // return Presets.Linear(this.weight); + // case CurvePresets.Power: + // this.curveMax = this.weight; + // return Presets.Power(2.0f, this.weight); + // case CurvePresets.Sqrt: + // this.curveMax = this.weight; + // return Presets.Power(0.5f, this.weight); + // case CurvePresets.Reciprocal: + // this.curveMax = 1 / 0.01f * this.weight; + // return Presets.Reciprocal(this.weight); + // default: + // this.curveMax = weight; + // return AnimationCurve.Constant(0, 1, weight); + // } + // } + + public static class Presets { + private const int samples = 32; + public static AnimationCurve Linear(float weight) { + return AnimationCurve.Linear(0f, 0f, 1000f, weight * 1000); + } + public static AnimationCurve Power(float exponent, float weight) { + // build keyframes + Keyframe[] keys = new Keyframe[samples]; + for (int i = 0; i < samples; i++) { + float t = i / (float)(samples - 1); + float v = Mathf.Pow(t, exponent) * weight; + keys[i] = new Keyframe(t, v); + } + + AnimationCurve curve = new(keys); + + // set tangent modes for each key to Auto (smooth). Use Linear if you prefer straight segments. + for (int i = 0; i < curve.length; i++) { + AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Auto); + AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Auto); + } + + return curve; + } + public static AnimationCurve Reciprocal(float weight) { + int samples = 128; + float xMin = 0.001f; + float xMax = 1; + var keys = new Keyframe[samples]; + for (int i = 0; i < samples; i++) { + float t = i / (float)(samples - 1); + float x = Mathf.Lerp(xMin, xMax, t); + float y = 1f / x * weight; + keys[i] = new Keyframe(x, y); + } + var curve = new AnimationCurve(keys); + for (int i = 0; i < curve.length; i++) { + AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Linear); + AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Linear); + } + return curve; + } + } +} + +[Serializable] +public class Receiver { + [NonSerialized] + public INucleus nucleus; + //public int nucleusId; + + public Receiver(INucleus nucleus) { + this.nucleus = nucleus; + //this.nucleusId = nucleus.id; + } + + public bool Rebuild(NanoBrain brain) { + if (brain == null) { + return false; + } + + // Use SerializedReference instead? + // foreach (Nucleus nucleus in brain.nuclei) { + // if (nucleus.id == this.nucleusId) { + // this.nucleus = nucleus; + // return true; + // } + // } + //Debug.LogWarning($"Receiver deserialization error: could not find nucleus with id {this.nucleusId}"); + return false; + } } \ No newline at end of file diff --git a/Assets/NanoBrain/Perceptoid.cs b/Assets/NanoBrain/Perceptoid.cs index 3f1d0b1..85f0bc1 100644 --- a/Assets/NanoBrain/Perceptoid.cs +++ b/Assets/NanoBrain/Perceptoid.cs @@ -1,98 +1,103 @@ -using UnityEngine; - -[System.Serializable] -public class Perceptoid : Neuroid { - // A neuroid which has no neurons as input - // But receives value from a receptor - public Receptor receptor; - public string baseName; - - public int thingId; - - //[SerializeField] - // Needs serialization!!!! - [SerializeReference] - public PercepteiArray array; - - #region Serialization - - [SerializeField] - public int thingType; - - public override void Rebuild(NanoBrain brain) { - base.Rebuild(brain); - this.receptor = Receptor.GetReceptor(brain, thingType); - this.receptor.perceptei.Add(this); - if (string.IsNullOrEmpty(this.baseName)) - this.baseName = this.name; - } - - public override void Deserialize(Nucleus nucleus) { - base.Deserialize(nucleus); - - if (nucleus is Perceptoid perceptoid) - this.receptor.thingType = perceptoid.thingType; - - // Point all receivers to this perceptoid instead of the default nucleus - foreach (Receiver receiver in nucleus.receivers) { - foreach (Synapse synapse in receiver.nucleus.synapses) { - if (synapse.nucleus == nucleus) - synapse.nucleus = this; - } - } - // Point all synapses to this perceptoid instead of the default nucleus - foreach (Synapse synapse in nucleus.synapses) { - foreach (Receiver receiver in synapse.nucleus.receivers) { - if (receiver.nucleus == nucleus) - receiver.nucleus = this; - } - } - // Copy all the synapses - this.synapses = nucleus.synapses; - // Copy all receivers - this.receivers = nucleus.receivers; - } - - #endregion Serialization - - public Perceptoid(NanoBrain brain, int thingType, string name = "sensor") : base(name) { - this.brain = brain; - if (this.brain != null) { - this.brain.perceptei.Add(this); - } - else - Debug.LogError("No neuroid network"); - - this.nucleusType = nameof(Perceptoid); - this.name = name; - this.baseName = name; - this.thingType = thingType; - this.receptor = Receptor.GetReceptor(brain, thingType); - this.receptor.perceptei.Add(this); - this.array = new PercepteiArray(this); - } - - public Perceptoid(PercepteiArray array) : base(array.name) { - this.array = array; - Perceptoid source = array.perceptei[0]; - this.brain = source.brain; - if (this.brain != null) { - this.brain.perceptei.Add(this); - } - else - Debug.LogError("No neuroid network"); - - this.nucleusType = nameof(Perceptoid); - this.name = source.baseName; - this.baseName = source.baseName; - this.thingType = source.thingType; - this.receptor = Receptor.GetReceptor(this.brain, this.thingType); - this.receptor.perceptei.Add(this); - } - - public override void UpdateState() { - Vector3 result = this.receptor.localPosition; - UpdateResult(result); - } - -} +using UnityEngine; + +[System.Serializable] +public class Perceptoid : Neuroid { + // A neuroid which has no neurons as input + // But receives value from a receptor + + public NanoBrain brain; + public Receptor receptor; + public string baseName; + + public int thingId; + + //[SerializeField] + // Needs serialization!!!! + [SerializeReference] + public PercepteiArray array; + + #region Serialization + + [SerializeField] + public int thingType; + + public override void Rebuild(NanoBrain brain) { + base.Rebuild(brain); + this.receptor = Receptor.GetReceptor(brain, thingType); + this.receptor.perceptei.Add(this); + if (string.IsNullOrEmpty(this.baseName)) + this.baseName = this.name; + } + + public override void Deserialize(Nucleus nucleus) { + base.Deserialize(nucleus); + + if (nucleus is Perceptoid perceptoid) + this.receptor.thingType = perceptoid.thingType; + + // Point all receivers to this perceptoid instead of the default nucleus + foreach (Receiver receiver in nucleus.receivers) { + foreach (Synapse synapse in receiver.nucleus.synapses) { + if (synapse.nucleus == nucleus) + synapse.nucleus = this; + } + } + // Point all synapses to this perceptoid instead of the default nucleus + foreach (Synapse synapse in nucleus.synapses) { + foreach (Receiver receiver in synapse.nucleus.receivers) { + if (receiver.nucleus == nucleus) + receiver.nucleus = this; + } + } + // Copying disabled for now + // // Copy all the synapses + // this.synapses = nucleus.synapses; + // // Copy all receivers + // this.receivers = nucleus.receivers; + } + + #endregion Serialization + + public Perceptoid(NanoBrain brain, int thingType, string name = "sensor") : base(name) { + this.brain = brain; + this.cluster = brain.cluster; + if (this.cluster != null) { + brain.perceptei.Add(this); + } + else + Debug.LogError("No neuroid network"); + + this.nucleusType = nameof(Perceptoid); + this.name = name; + this.baseName = name; + this.thingType = thingType; + this.receptor = Receptor.GetReceptor(brain, thingType); + this.receptor.perceptei.Add(this); + this.array = new PercepteiArray(this); + } + + public Perceptoid(PercepteiArray array) : base(array.name) { + this.array = array; + Perceptoid source = array.perceptei[0]; + this.brain = source.brain; + this.cluster = source.cluster; + if (this.brain != null) { + this.brain.perceptei.Add(this); + } + else + Debug.LogError("No neuroid network"); + + this.nucleusType = nameof(Perceptoid); + this.name = source.baseName; + this.baseName = source.baseName; + this.thingType = source.thingType; + this.receptor = Receptor.GetReceptor(this.brain, this.thingType); + this.receptor.perceptei.Add(this); + } + + public override void UpdateState() { + Vector3 result = this.receptor.localPosition; + UpdateResult(result); + } + +} diff --git a/Assets/NanoBrain/Receptor.cs b/Assets/NanoBrain/Receptor.cs index 3e63760..ed8ba96 100644 --- a/Assets/NanoBrain/Receptor.cs +++ b/Assets/NanoBrain/Receptor.cs @@ -1,82 +1,82 @@ -using System.Collections.Generic; -using UnityEngine; -using LinearAlgebra; - -public class Receptor { - /// - /// The list of perceptoid which can process stimuli from this receptor - /// - public List perceptei = new(); - - private int _thingType = 0; - public int thingType { - get { return _thingType; } - set { - _thingType = value; - foreach (Perceptoid perceptoid in perceptei) { - perceptoid.thingType = _thingType; - } - } - } - public Vector3 localPosition; - public float distanceResolution = 0.1f; - public float directionResolution = 5; - - public Receptor(NanoBrain brain, int thingType) { - this.thingType = thingType; - //this.perceptei.Add(perceptoid); - brain.receptors.Add(this); - } - - public static Receptor GetReceptor(NanoBrain brain, int thingType) { - foreach (Receptor receptor in brain.receptors) { - if (thingType == 0 || receptor.thingType == thingType) - return receptor; - } - Receptor newReceptor = new(brain, thingType); - return newReceptor; - } - - public virtual void ProcessStimulus(int thingId, Vector3 newLocalPositionVector, string thingName = null) { - this.localPosition = newLocalPositionVector; - - Perceptoid selectedPerceptoid = null; - foreach (Perceptoid perceptoid in this.perceptei) { - if (perceptoid.thingId == thingId) { - // We found an existing perceptoid for this thing - selectedPerceptoid = perceptoid; - // Do not look any further - - break; - } - else if (perceptoid.isSleeping) { - // A sleeping perceptoid is not active and can therefore always be reused - selectedPerceptoid = perceptoid; - // Look further because we could find a existing perceptoid for this thing - } - - else if (selectedPerceptoid == null) { - // If we haven't found a perceptoid yet, just start by taking the first - selectedPerceptoid = perceptoid; - } - - else if (selectedPerceptoid.isSleeping == false) { - // If no existing or sleeping perceptoid is found, we look for the perceptoid - // we the furthest (least interesting) stimulus - if (perceptoid.receptor.localPosition.magnitude < selectedPerceptoid.receptor.localPosition.magnitude) { - Debug.Log($"{selectedPerceptoid.name} {selectedPerceptoid.receptor.localPosition.magnitude} {perceptoid.receptor.localPosition.magnitude} "); - selectedPerceptoid = perceptoid; - } - } - } - if (selectedPerceptoid == null) { - Debug.Log("No perceptoid selected, stimulus is ignored"); - return; - } - // Debug.Log($"Stimulus {thingType} {thingId} {selectedPerceptoid.name}"); - selectedPerceptoid.thingId = thingId; - if (thingName != null) - selectedPerceptoid.name = selectedPerceptoid.baseName + " " + thingName; - selectedPerceptoid.UpdateState(); - } +using System.Collections.Generic; +using UnityEngine; +using LinearAlgebra; + +public class Receptor { + /// + /// The list of perceptoid which can process stimuli from this receptor + /// + public List perceptei = new(); + + private int _thingType = 0; + public int thingType { + get { return _thingType; } + set { + _thingType = value; + foreach (Perceptoid perceptoid in perceptei) { + perceptoid.thingType = _thingType; + } + } + } + public Vector3 localPosition; + public float distanceResolution = 0.1f; + public float directionResolution = 5; + + public Receptor(NanoBrain brain, int thingType) { + this.thingType = thingType; + //this.perceptei.Add(perceptoid); + brain.receptors.Add(this); + } + + public static Receptor GetReceptor(NanoBrain brain, int thingType) { + foreach (Receptor receptor in brain.receptors) { + if (thingType == 0 || receptor.thingType == thingType) + return receptor; + } + Receptor newReceptor = new(brain, thingType); + return newReceptor; + } + + public virtual void ProcessStimulus(int thingId, Vector3 newLocalPositionVector, string thingName = null) { + this.localPosition = newLocalPositionVector; + + Perceptoid selectedPerceptoid = null; + foreach (Perceptoid perceptoid in this.perceptei) { + if (perceptoid.thingId == thingId) { + // We found an existing perceptoid for this thing + selectedPerceptoid = perceptoid; + // Do not look any further + + break; + } + else if (perceptoid.isSleeping) { + // A sleeping perceptoid is not active and can therefore always be reused + selectedPerceptoid = perceptoid; + // Look further because we could find a existing perceptoid for this thing + } + + else if (selectedPerceptoid == null) { + // If we haven't found a perceptoid yet, just start by taking the first + selectedPerceptoid = perceptoid; + } + + else if (selectedPerceptoid.isSleeping == false) { + // If no existing or sleeping perceptoid is found, we look for the perceptoid + // we the furthest (least interesting) stimulus + if (perceptoid.receptor.localPosition.magnitude < selectedPerceptoid.receptor.localPosition.magnitude) { + Debug.Log($"{selectedPerceptoid.name} {selectedPerceptoid.receptor.localPosition.magnitude} {perceptoid.receptor.localPosition.magnitude} "); + selectedPerceptoid = perceptoid; + } + } + } + if (selectedPerceptoid == null) { + Debug.Log("No perceptoid selected, stimulus is ignored"); + return; + } + // Debug.Log($"Stimulus {thingType} {thingId} {selectedPerceptoid.name}"); + selectedPerceptoid.thingId = thingId; + if (thingName != null) + selectedPerceptoid.name = selectedPerceptoid.baseName + " " + thingName; + selectedPerceptoid.UpdateState(); + } } \ No newline at end of file diff --git a/Assets/NanoBrain/VisualEditor/Editor/BrainPickerWindow.cs b/Assets/NanoBrain/VisualEditor/Editor/BrainPickerWindow.cs new file mode 100644 index 0000000..d80c282 --- /dev/null +++ b/Assets/NanoBrain/VisualEditor/Editor/BrainPickerWindow.cs @@ -0,0 +1,72 @@ +using UnityEditor; +using UnityEngine; +using System; +using System.Linq; + +public class BrainPickerWindow : EditorWindow +{ + private Vector2 scroll; + private NanoBrain[] items = new NanoBrain[0]; + private Action onPicked; + private string search = ""; + + public static void ShowPicker(Action onPicked, string title = "Select NanoBrain") + { + var w = CreateInstance(); + w.titleContent = new GUIContent(title); + w.minSize = new Vector2(360, 320); + w.onPicked = onPicked; + w.RefreshList(); + w.ShowModalUtility(); // modal dialog + } + + private void OnEnable() => RefreshList(); + + private void RefreshList() + { + var guids = AssetDatabase.FindAssets("t:NanoBrain"); + items = guids + .Select(g => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(g))) + .Where(b => b != null) + .OrderBy(b => b.name) + .ToArray(); + } + + private void OnGUI() + { + EditorGUILayout.Space(); + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Choose NanoBrain:", EditorStyles.boldLabel); + if (GUILayout.Button("Refresh", GUILayout.Width(80))) RefreshList(); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + search = EditorGUILayout.TextField(search); + + EditorGUILayout.Space(); + scroll = EditorGUILayout.BeginScrollView(scroll); + foreach (var it in items) + { + if (!string.IsNullOrEmpty(search) && it.name.IndexOf(search, StringComparison.OrdinalIgnoreCase) < 0) + continue; + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(EditorGUIUtility.ObjectContent(it, typeof(NanoBrain)), GUILayout.Height(20)); + if (GUILayout.Button("Select", GUILayout.Width(70))) + { + onPicked?.Invoke(it); + Close(); + return; + } + EditorGUILayout.EndHorizontal(); + } + EditorGUILayout.EndScrollView(); + + EditorGUILayout.Space(); + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button("Cancel")) { onPicked?.Invoke(null); Close(); } + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + } +} diff --git a/Assets/NanoBrain/VisualEditor/Editor/BrainPickerWindow.cs.meta b/Assets/NanoBrain/VisualEditor/Editor/BrainPickerWindow.cs.meta new file mode 100644 index 0000000..b2de114 --- /dev/null +++ b/Assets/NanoBrain/VisualEditor/Editor/BrainPickerWindow.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9197e2d322d23b5798ab4aef729815b0 \ No newline at end of file diff --git a/Assets/NanoBrain/VisualEditor/Editor/ClusterInspector.cs b/Assets/NanoBrain/VisualEditor/Editor/ClusterInspector.cs new file mode 100644 index 0000000..e078ad1 --- /dev/null +++ b/Assets/NanoBrain/VisualEditor/Editor/ClusterInspector.cs @@ -0,0 +1,633 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEditor; + +using UnityEngine; +using UnityEngine.UIElements; + +[CustomEditor(typeof(Cluster))] +public class ClusterInspector : Editor { + protected static VisualElement mainContainer; + protected static VisualElement inspectorContainer; + + protected bool breakOnWake = false; + + #region Start + + public override VisualElement CreateInspectorGUI() { + Cluster cluster = target as Cluster; + + serializedObject.Update(); + + VisualElement root = new(); + //root.style.flexDirection = FlexDirection.Row; // 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, + height = 450, + } + }; + GraphView graph = new(); + graph.style.flexGrow = 1; + + inspectorContainer = new VisualElement { + // name = "inspector" + }; + + mainContainer.Add(graph); + 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 (cluster != null) + graph.SetGraph(null, cluster, cluster.output, inspectorContainer); + else + Debug.LogWarning(" No brain!"); + + serializedObject.ApplyModifiedProperties(); + return root; + } + + public class GraphView : VisualElement { + Cluster brain; + SerializedObject serializedBrain; + INucleus currentNucleus; + GameObject gameObject; + private List layers = new(); + private readonly Dictionary neuroidPositions = new(); + + Vector2 pan = Vector2.zero; + //float zoom = 1f; + bool draggingCanvas = false; + Vector2 lastMouse; + ClusterWrapper 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(GameObject gameObject, Cluster brain, Nucleus nucleus, VisualElement inspectorContainer) { + this.gameObject = gameObject; + this.brain = brain; + if (Application.isPlaying == false) + 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; + + INucleus selectedNucleus = this.currentNucleus; + if (selectedNucleus == null) + return; + NeuroidLayer currentLayer = new() { ix = layerIx }; + + if (selectedNucleus.receivers != null) { + foreach (Receiver receiver in selectedNucleus.receivers) { + INucleus 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) { + INucleus 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, INucleus 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; + + } + + 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; + + if (Application.isPlaying == false) + serializedBrain.Update(); + + Handles.BeginGUI(); + DrawGraph(); + Handles.EndGUI(); + + } + + private void DrawGraph() { + float size = 20; + Vector3 position = new(150, 210, 0); + + DrawReceivers(this.currentNucleus, position, size); + DrawSynapses(this.currentNucleus, position, size); + + // Draw selected Nucleus + Handles.color = Color.white; + Handles.DrawSolidDisc(position, Vector3.forward, size + 2); + DrawNucleus(this.currentNucleus, position, this.currentNucleus.outputValue.magnitude, 20); + } + + private void DrawReceivers(INucleus nucleus, Vector3 parentPos, float size) { + int nodeCount = nucleus.receivers.Count; + + // Determine the maximum value in this layer + // This is used to 'scale' the output value colors of the nuclei + float maxValue = 0; + foreach (Receiver receiver in nucleus.receivers) { + if (receiver.nucleus is Neuroid neuroid) { + float value = neuroid.outputValue.magnitude; + if (value > maxValue) + maxValue = value; + } + } + + // Determine the spacing of the nuclei in the layer + float spacing = 400f / nodeCount; + float margin = 10 + spacing / 2; + + int row = 0; + foreach (Receiver receiver in nucleus.receivers) { + INucleus receiverNucleus = receiver.nucleus; + if (receiverNucleus == null) + continue; + + Vector3 pos = new(50, margin + row * spacing, 0.0f); + Handles.color = Color.white; + Handles.DrawLine(parentPos, pos); + + DrawNucleus(receiverNucleus, pos, maxValue, size); + row++; + } + } + + private void DrawSynapses(INucleus nucleus, Vector3 parentPos, float size) { + int nodeCount = nucleus.synapses.Count; + + // Determine the maximum value in this layer + // This is used to 'scale' the output value colors of the nuclei + float maxValue = 0; + foreach (Synapse receiver in nucleus.synapses) { + if (receiver.nucleus is Neuroid neuroid) { + float value = neuroid.outputValue.magnitude; + if (value > maxValue) + maxValue = value; + } + } + + // Determine the spacing of the nuclei in the layer + float spacing = 400f / nodeCount; + float margin = 10 + spacing / 2; + + int row = 0; + List drawnArrays = new(); + foreach (Synapse synapse in nucleus.synapses) { + Vector3 pos = new(250, margin + row * spacing, 0.0f); + Handles.color = Color.white; + Handles.DrawLine(parentPos, pos); + // if (synapse.nucleus is Perceptoid perceptoid && perceptoid.array != null) { + // // if (drawnArrays.Contains(perceptoid.array)) + // // // We already drawn this array + // // continue; + + // drawnArrays.Add(perceptoid.array); + // DrawArray(perceptoid.array, pos, size); + // } + // else { + + DrawNucleus(synapse.nucleus, pos, maxValue, size); + row++; + // } + } + } + + private void DrawNucleus(INucleus nucleus, Vector3 position, float maxValue, float size) { + if (nucleus.isSleeping) + Handles.color = Color.darkRed; + else { + if (Application.isPlaying) { + float brightness = nucleus.outputValue.magnitude / maxValue; + Handles.color = new Color(brightness, brightness, brightness, 1f); + } + else + Handles.color = Color.black; + } + Handles.DrawSolidDisc(position, Vector3.forward, size); + + Handles.color = Color.white; + // Position the label in front of the disc + Vector3 labelPosition = position + (Vector3.forward * 0.1f); + + GUIStyle style = new(EditorStyles.label) { + alignment = TextAnchor.MiddleCenter, + normal = { textColor = Color.white }, + fontStyle = FontStyle.Bold, + }; + if (nucleus is Perceptoid perceptoid) { + if (perceptoid.array == null || perceptoid.array.perceptei == null || perceptoid.array.perceptei.Length == 0) + perceptoid.array = new PercepteiArray(perceptoid); + + if (perceptoid.array.perceptei.Length > 1) { + Handles.Label(labelPosition, perceptoid.array.perceptei.Length.ToString(), style); + } + } + + style.alignment = TextAnchor.UpperCenter; + Vector3 labelPos = position - Vector3.down * (size + 0.2f); // below disc along up axis + Handles.Label(labelPos, nucleus.name, style); + + Rect neuronRect = new(position.x - size, position.y - size, size * 2, size * 2); + int id = GUIUtility.GetControlID(FocusType.Passive); + Event e = Event.current; + EventType et = e.GetTypeForControl(id); + if (e != null && neuronRect.Contains(e.mousePosition)) { + // Process Hover + HandleMouseHover(nucleus, neuronRect); + // Process click + if (e.type == EventType.MouseDown && e.button == 0) { + // Consume the event so the scene doesn't also handle it + e.Use(); + HandleClicked(nucleus); + } + } + } + + private void DrawArray(PercepteiArray array, Vector3 position, float size) { + Vector3 offset = new(size / 4, size / 4, 0); + Handles.color = Color.black; + Handles.DrawSolidDisc(position, Vector3.forward, size); + + GUIStyle style = new(EditorStyles.label) { + alignment = TextAnchor.UpperCenter, + normal = { textColor = Color.white }, + fontStyle = FontStyle.Bold + }; + Handles.Label(position, array.perceptei.Length.ToString(), style); + Vector3 labelPos = position - Vector3.down * (size + 0.2f); // below disc along up axis + Handles.Label(labelPos, array.name, style); + + // To do: add HandleClick (see above) to expand the array + } + + private void HandleMouseHover(INucleus nucleus, Rect rect) { + GUIContent tooltip; + if (nucleus is Perceptoid perceptoid) { + if (perceptoid.receptor != null) { + tooltip = new( + $"{perceptoid.name}" + + $"\nType {perceptoid.receptor.thingType}" + + $" Thing {perceptoid.thingId}" + + $"\nValue: {nucleus.outputValue}"); + } + else { + tooltip = new( + $"{perceptoid.name}" + + $"\nThing {perceptoid.thingId}" + + $"\nValue: {nucleus.outputValue}"); + } + } + else { + tooltip = new( + $"{nucleus.name}" + + $"\nsynapse count {nucleus.synapses.Count}" + + $"\nValue: {nucleus.outputValue}"); + } + + 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(INucleus 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(); + + if (this.currentNucleus == null) + return; + + this.currentNucleus.name = EditorGUILayout.TextField(this.currentNucleus.name); + if (this.currentNucleus is Perceptoid perceptoid) { + perceptoid.receptor.thingType = EditorGUILayout.IntField("Thing Type", perceptoid.receptor.thingType); + + if (perceptoid.array == null || perceptoid.array.perceptei == null || perceptoid.array.perceptei.Length == 0) + perceptoid.array = new PercepteiArray(perceptoid); + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.IntField("Array size", perceptoid.array.perceptei.Length); + if (GUILayout.Button("Add")) + perceptoid.array.AddPerceptoid(); + if (GUILayout.Button("Del")) + perceptoid.array.RemovePerceptoid(); + EditorGUILayout.EndHorizontal(); + } + else if (this.currentNucleus is Neuroid neuroid) { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Activation Curve", GUILayout.Width(150)); + if (neuroid.curveMax > 0) + EditorGUILayout.CurveField(neuroid.curve, Color.cyan, new Rect(0, 0, 1, neuroid.curveMax)); + else + EditorGUILayout.CurveField(neuroid.curve, Color.cyan, new Rect(0, neuroid.curveMax, 1, -neuroid.curveMax)); + neuroid.curvePreset = (Neuroid.CurvePresets)EditorGUILayout.EnumPopup(neuroid.curvePreset, GUILayout.Width(100)); + EditorGUILayout.EndHorizontal(); + } + + if (Application.isPlaying) + EditorGUILayout.FloatField("Output", this.currentNucleus.outputValue.magnitude); + else + EditorGUILayout.LabelField(" "); + + if (this.currentNucleus.synapses.Count > 0) { + Synapse[] synapses = this.currentNucleus.synapses.ToArray(); + foreach (Synapse synapse in synapses) { + if (synapse.nucleus != null) { + EditorGUILayout.Space(); + + EditorGUI.BeginDisabledGroup(synapse.nucleus.isSleeping); + if (Application.isPlaying) + EditorGUILayout.FloatField(synapse.nucleus.name, synapse.nucleus.outputValue.magnitude * synapse.weight); + else { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(synapse.nucleus.name); + // if (synapse.nucleus is Perceptoid perceptoid) { + // if (perceptoid.array == null || perceptoid.array.perceptei == null || perceptoid.array.perceptei.Length == 0) { + // perceptoid.array = new PercepteiArray(perceptoid); + // } + // EditorGUILayout.IntField(perceptoid.array.perceptei.Length); + // if (GUILayout.Button("Add")) + // perceptoid.array.AddPerceptoid(); + // } + if (GUILayout.Button("Disconnect")) + synapse.nucleus.RemoveReceiver(this.currentNucleus); + EditorGUILayout.EndHorizontal(); + } + + EditorGUI.indentLevel++; + synapse.weight = EditorGUILayout.FloatField("Weight", synapse.weight); + EditorGUI.indentLevel--; + EditorGUI.EndDisabledGroup(); + } + } + } + + EditorGUILayout.Space(); + + ConnectNucleus(this.currentNucleus); + if (GUILayout.Button("Add Input Neuron")) + AddInputNeuron(this.currentNucleus); + // if (GUILayout.Button("Add Input Perceptoid")) + // AddPerceptoid(this.currentNucleus); + if (GUILayout.Button("Add Input Cluster")) + AddCluster(this.currentNucleus); + + EditorGUILayout.Space(); + + if (GUILayout.Button("Delete this neuron")) + DeleteNeuron(this.currentNucleus); + + //DisconnectNucleus(this.currentNucleus); + + if (this.gameObject != null) { + Vector3 worldVector = this.gameObject.transform.TransformVector(this.currentNucleus.outputValue); + Debug.DrawRay(this.gameObject.transform.position, worldVector, Color.yellow); + } + }); + + inspectorContainer.Add(container); + } + + protected virtual void AddInputNeuron(INucleus nucleus) { + Neuroid newNeuroid = new(this.brain.cluster, "New neuron"); + newNeuroid.AddReceiver(nucleus); + this.currentNucleus = newNeuroid; + BuildLayers(); + } + + protected virtual void DeleteNeuron(INucleus nucleus) { + if (nucleus == null) + return; + if (nucleus.cluster != null) + this.currentNucleus = nucleus.cluster.output; + foreach (Receiver receiver in nucleus.receivers) { + if (receiver.nucleus != null) { + this.currentNucleus = receiver.nucleus; + break; + } + } + Nucleus.Delete(nucleus); + BuildLayers(); + } + + // protected virtual void AddPerceptoid(INucleus nucleus) { + // Perceptoid newPerceptoid = new(this.brain, 0, "New Perceptoid"); + // newPerceptoid.AddReceiver(nucleus); + // this.currentNucleus = newPerceptoid; + // BuildLayers(); + // } + + protected virtual void AddCluster(INucleus nucleus) { + BrainPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster"); + } + + private void OnClusterPicked(INucleus nucleus, NanoBrain brain) { + NanoBrain brainInstance = Instantiate(brain); + brainInstance.AddReceiver(nucleus); + } + + protected virtual void ConnectNucleus(INucleus nucleus) { + if (this.currentNucleus.cluster == null) + return; + + IEnumerable synapseNuclei = this.currentNucleus.synapses.Select(synapse => synapse.nucleus.name); + //IEnumerable perceptei = this.currentNucleus.brain.perceptei.Select(i => i.name).Except(synapseNuclei); + IEnumerable nuclei = this.currentNucleus.cluster.nuclei.Select(i => i.name).Except(synapseNuclei); + //string[] names = perceptei.Concat(nuclei).ToArray(); + string[] names = nuclei.ToArray(); + int selectedIndex = -1; + selectedIndex = EditorGUILayout.Popup("Connect to", selectedIndex, names); + if (selectedIndex >= 0) { + // if (selectedIndex < perceptei.Count()) { + // Nucleus n = this.currentNucleus.brain.perceptei[selectedIndex]; + // n.AddReceiver(this.currentNucleus); + // } + // else { + // Nucleus n = this.currentNucleus.brain.nuclei[selectedIndex - perceptei.Count()]; + // n.AddReceiver(this.currentNucleus); + // } + Nucleus n = this.currentNucleus.cluster.nuclei[selectedIndex]; + n.AddReceiver(this.currentNucleus); + } + } + + protected virtual void DisconnectNucleus(Nucleus nucleus) { + if (this.currentNucleus.cluster == null) + return; + string[] names = this.currentNucleus.synapses.Select(synapse => synapse.nucleus.name).ToArray(); + int selectedIndex = -1; + selectedIndex = EditorGUILayout.Popup("Disconnect from", selectedIndex, names); + //if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.brain.perceptei.Count) { + if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.cluster.nuclei.Count) { + Synapse synapse = this.currentNucleus.synapses[selectedIndex]; + synapse.nucleus.RemoveReceiver(this.currentNucleus); + } + } + } + + #endregion Start + + #region Update + + private void UpdateLayout(float containerWidth) { + if (containerWidth > 600f) { + mainContainer.style.flexDirection = FlexDirection.Row; + inspectorContainer.style.width = 300; // 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 + } + } + + #endregion Update +} + +public class NeuroidLayer { + public int ix = 0; + public List neuroids = new(); +} + +public class ClusterWrapper : ScriptableObject { + // expose fields that map to GraphNode + //public string title; + public Vector2 position; + INucleus node; + Cluster graph; // needed to write back and mark dirty + + public ClusterWrapper Init(INucleus node, Cluster graphAsset) { + this.node = node; + this.graph = graphAsset; + //this.title = " A " + node.name; + //position = node.position; + return this; + } + void OnValidate() { + if (node != null) { + //node.name = title; + //node.position = position; +#if UNITY_EDITOR + if (graph != null) + UnityEditor.EditorUtility.SetDirty(graph); +#endif + } + } +} \ No newline at end of file diff --git a/Assets/NanoBrain/VisualEditor/Editor/ClusterInspector.cs.meta b/Assets/NanoBrain/VisualEditor/Editor/ClusterInspector.cs.meta new file mode 100644 index 0000000..a1a18f5 --- /dev/null +++ b/Assets/NanoBrain/VisualEditor/Editor/ClusterInspector.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1fc1fb7db9f7ad54a87d31313e7f457d \ No newline at end of file diff --git a/Assets/NanoBrain/VisualEditor/Editor/NanoBrainComponent_Editor.cs b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainComponent_Editor.cs index ae876a8..749b3d5 100644 --- a/Assets/NanoBrain/VisualEditor/Editor/NanoBrainComponent_Editor.cs +++ b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainComponent_Editor.cs @@ -1,103 +1,103 @@ -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; - - protected NanoBrainComponent component; - private SerializedProperty brainProp; - - public void OnEnable() { - component = target as NanoBrainComponent; - - if (Application.isPlaying == false) - brainProp = serializedObject.FindProperty(nameof(NanoBrainComponent.defaultBrain)); - } - - public override VisualElement CreateInspectorGUI() { - //NanoBrainComponent component = target as NanoBrainComponent; - NanoBrain brain = Application.isPlaying ? component.brain : component.defaultBrain; - - if (Application.isPlaying == false) - 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")); - - if (Application.isPlaying == false) { - PropertyField brainField = new(brainProp) { - 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(component.gameObject, brain, brain.root, inspectorContainer); - // else - // Debug.LogWarning(" No brain!"); - - if (Application.isPlaying == false) - 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 - } - } - +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; + + protected NanoBrainComponent component; + private SerializedProperty brainProp; + + public void OnEnable() { + component = target as NanoBrainComponent; + + if (Application.isPlaying == false) + brainProp = serializedObject.FindProperty(nameof(NanoBrainComponent.defaultBrain)); + } + + public override VisualElement CreateInspectorGUI() { + //NanoBrainComponent component = target as NanoBrainComponent; + NanoBrain brain = Application.isPlaying ? component.brain : component.defaultBrain; + + if (Application.isPlaying == false) + 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")); + + if (Application.isPlaying == false) { + PropertyField brainField = new(brainProp) { + 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(component.gameObject, brain, brain.output, inspectorContainer); + // else + // Debug.LogWarning(" No brain!"); + + if (Application.isPlaying == false) + 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/NanoBrainInspector.cs b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs index 8cd7114..7290c03 100644 --- a/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs +++ b/Assets/NanoBrain/VisualEditor/Editor/NanoBrainInspector.cs @@ -59,7 +59,7 @@ public class NanoBrainInspector : Editor { }); if (brain != null) - graph.SetGraph(null, brain, brain.root, inspectorContainer); + graph.SetGraph(null, brain, brain.output, inspectorContainer); else Debug.LogWarning(" No brain!"); @@ -70,10 +70,10 @@ public class NanoBrainInspector : Editor { public class GraphView : VisualElement { NanoBrain brain; SerializedObject serializedBrain; - Nucleus currentNucleus; + INucleus currentNucleus; GameObject gameObject; private List layers = new(); - private readonly Dictionary neuroidPositions = new(); + private readonly Dictionary neuroidPositions = new(); Vector2 pan = Vector2.zero; //float zoom = 1f; @@ -127,14 +127,14 @@ public class NanoBrainInspector : Editor { this.layers = new(); int layerIx = 0; - Nucleus selectedNucleus = this.currentNucleus; + INucleus 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; + INucleus outputNeuroid = receiver.nucleus; if (outputNeuroid != null) { AddToLayer(currentLayer, outputNeuroid); // Debug.Log($"layer {layerIx} nucleus {outputNeuroid.name}"); @@ -156,7 +156,7 @@ public class NanoBrainInspector : Editor { if (selectedNucleus.synapses != null) { foreach (Synapse synapse in selectedNucleus.synapses) { - Nucleus input = synapse.nucleus; + INucleus input = synapse.nucleus; AddToLayer(currentLayer, input); // Debug.Log($"layer {layerIx} nucleus {input.name}"); } @@ -166,11 +166,11 @@ public class NanoBrainInspector : Editor { } } - private void AddToLayer(NeuroidLayer layer, Nucleus nucleus) { + private void AddToLayer(NeuroidLayer layer, INucleus nucleus) { if (nucleus == null) return; layer.neuroids.Add(nucleus); - nucleus.layerIx = layer.ix; + //nucleus.layerIx = layer.ix; // Store its position Vector2Int neuroidPosition = new(layer.ix, layer.neuroids.Count - 1); neuroidPositions[nucleus] = neuroidPosition; @@ -217,7 +217,7 @@ public class NanoBrainInspector : Editor { DrawNucleus(this.currentNucleus, position, this.currentNucleus.outputValue.magnitude, 20); } - private void DrawReceivers(Nucleus nucleus, Vector3 parentPos, float size) { + private void DrawReceivers(INucleus nucleus, Vector3 parentPos, float size) { int nodeCount = nucleus.receivers.Count; // Determine the maximum value in this layer @@ -237,7 +237,7 @@ public class NanoBrainInspector : Editor { int row = 0; foreach (Receiver receiver in nucleus.receivers) { - Nucleus receiverNucleus = receiver.nucleus; + INucleus receiverNucleus = receiver.nucleus; if (receiverNucleus == null) continue; @@ -250,7 +250,7 @@ public class NanoBrainInspector : Editor { } } - private void DrawSynapses(Nucleus nucleus, Vector3 parentPos, float size) { + private void DrawSynapses(INucleus nucleus, Vector3 parentPos, float size) { int nodeCount = nucleus.synapses.Count; // Determine the maximum value in this layer @@ -290,7 +290,7 @@ public class NanoBrainInspector : Editor { } } - private void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size) { + private void DrawNucleus(INucleus nucleus, Vector3 position, float maxValue, float size) { if (nucleus.isSleeping) Handles.color = Color.darkRed; else { @@ -358,7 +358,7 @@ public class NanoBrainInspector : Editor { // To do: add HandleClick (see above) to expand the array } - private void HandleMouseHover(Nucleus nucleus, Rect rect) { + private void HandleMouseHover(INucleus nucleus, Rect rect) { GUIContent tooltip; if (nucleus is Perceptoid perceptoid) { if (perceptoid.receptor != null) { @@ -391,7 +391,7 @@ public class NanoBrainInspector : Editor { GUI.Box(tooltipRect, tooltip); } - private void HandleClicked(Nucleus nucleus) { + private void HandleClicked(INucleus nucleus) { this.currentNucleus = nucleus; BuildLayers(); } @@ -484,6 +484,8 @@ public class NanoBrainInspector : Editor { AddInputNeuron(this.currentNucleus); if (GUILayout.Button("Add Input Perceptoid")) AddPerceptoid(this.currentNucleus); + if (GUILayout.Button("Add Input Cluster")) + AddCluster(this.currentNucleus); EditorGUILayout.Space(); @@ -501,18 +503,18 @@ public class NanoBrainInspector : Editor { inspectorContainer.Add(container); } - protected virtual void AddInputNeuron(Nucleus nucleus) { - Neuroid newNeuroid = new(this.brain, "New neuron"); + protected virtual void AddInputNeuron(INucleus nucleus) { + Neuroid newNeuroid = new(this.brain.cluster, "New neuron"); newNeuroid.AddReceiver(nucleus); this.currentNucleus = newNeuroid; BuildLayers(); } - protected virtual void DeleteNeuron(Nucleus nucleus) { + protected virtual void DeleteNeuron(INucleus nucleus) { if (nucleus == null) return; - if (nucleus.brain != null) - this.currentNucleus = nucleus.brain.root; + if (nucleus.cluster != null) + this.currentNucleus = nucleus.cluster.output; foreach (Receiver receiver in nucleus.receivers) { if (receiver.nucleus != null) { this.currentNucleus = receiver.nucleus; @@ -523,42 +525,55 @@ public class NanoBrainInspector : Editor { BuildLayers(); } - protected virtual void AddPerceptoid(Nucleus nucleus) { + protected virtual void AddPerceptoid(INucleus nucleus) { Perceptoid newPerceptoid = new(this.brain, 0, "New Perceptoid"); newPerceptoid.AddReceiver(nucleus); this.currentNucleus = newPerceptoid; BuildLayers(); } - protected virtual void ConnectNucleus(Nucleus nucleus) { - if (this.currentNucleus.brain == null) + protected virtual void AddCluster(INucleus nucleus) { + BrainPickerWindow.ShowPicker(brain => OnClusterPicked(nucleus, brain), "Select Cluster"); + } + + private void OnClusterPicked(INucleus nucleus, NanoBrain brain) { + NanoBrain brainInstance = Instantiate(brain); + brainInstance.AddReceiver(nucleus); + } + + protected virtual void ConnectNucleus(INucleus nucleus) { + if (this.currentNucleus.cluster == null) return; IEnumerable synapseNuclei = this.currentNucleus.synapses.Select(synapse => synapse.nucleus.name); - IEnumerable perceptei = this.currentNucleus.brain.perceptei.Select(i => i.name).Except(synapseNuclei); - IEnumerable nuclei = this.currentNucleus.brain.nuclei.Select(i => i.name).Except(synapseNuclei); - string[] names = perceptei.Concat(nuclei).ToArray(); + //IEnumerable perceptei = this.currentNucleus.brain.perceptei.Select(i => i.name).Except(synapseNuclei); + IEnumerable nuclei = this.currentNucleus.cluster.nuclei.Select(i => i.name).Except(synapseNuclei); + //string[] names = perceptei.Concat(nuclei).ToArray(); + string[] names = nuclei.ToArray(); int selectedIndex = -1; selectedIndex = EditorGUILayout.Popup("Connect to", selectedIndex, names); if (selectedIndex >= 0) { - if (selectedIndex < perceptei.Count()) { - Nucleus n = this.currentNucleus.brain.perceptei[selectedIndex]; + // if (selectedIndex < perceptei.Count()) { + // Nucleus n = this.currentNucleus.brain.perceptei[selectedIndex]; + // n.AddReceiver(this.currentNucleus); + // } + // else { + // Nucleus n = this.currentNucleus.brain.nuclei[selectedIndex - perceptei.Count()]; + // n.AddReceiver(this.currentNucleus); + // } + Nucleus n = this.currentNucleus.cluster.nuclei[selectedIndex]; n.AddReceiver(this.currentNucleus); - } - else { - Nucleus n = this.currentNucleus.brain.nuclei[selectedIndex - perceptei.Count()]; - n.AddReceiver(this.currentNucleus); - } } } protected virtual void DisconnectNucleus(Nucleus nucleus) { - if (this.currentNucleus.brain == null) + if (this.currentNucleus.cluster == null) return; string[] names = this.currentNucleus.synapses.Select(synapse => synapse.nucleus.name).ToArray(); int selectedIndex = -1; selectedIndex = EditorGUILayout.Popup("Disconnect from", selectedIndex, names); - if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.brain.perceptei.Count) { + //if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.brain.perceptei.Count) { + if (selectedIndex >= 0 && selectedIndex < this.currentNucleus.cluster.nuclei.Count) { Synapse synapse = this.currentNucleus.synapses[selectedIndex]; synapse.nucleus.RemoveReceiver(this.currentNucleus); } @@ -586,23 +601,30 @@ public class NanoBrainInspector : Editor { #endregion Update } +/* +public class NeuroidLayer { + public int ix = 0; + public List neuroids = new(); +} +*/ + public class GraphNodeWrapper : ScriptableObject { // expose fields that map to GraphNode - public string title; + //public string title; public Vector2 position; - Nucleus node; + INucleus node; NanoBrain graph; // needed to write back and mark dirty - public GraphNodeWrapper Init(Nucleus node, NanoBrain graphAsset) { + public GraphNodeWrapper Init(INucleus node, NanoBrain graphAsset) { this.node = node; this.graph = graphAsset; - this.title = " A " + node.name; + //this.title = " A " + node.name; //position = node.position; return this; } void OnValidate() { if (node != null) { - node.name = title; + //node.name = title; //node.position = position; #if UNITY_EDITOR if (graph != null) @@ -610,4 +632,4 @@ public class GraphNodeWrapper : ScriptableObject { #endif } } -} \ No newline at end of file +} diff --git a/Assets/NanoBrain/VisualEditor/NanoBrain.cs b/Assets/NanoBrain/VisualEditor/NanoBrain.cs index 93e5c00..c581ec6 100644 --- a/Assets/NanoBrain/VisualEditor/NanoBrain.cs +++ b/Assets/NanoBrain/VisualEditor/NanoBrain.cs @@ -1,92 +1,95 @@ -using System.Collections.Generic; -using UnityEngine; - -[CreateAssetMenu(menuName = "Passer/NanoBrain")] -public class NanoBrain : ScriptableObject, ISerializationCallbackReceiver { - - public string title; - public int count; - public Color color = Color.white; - public Texture2D texture; - - public List nuclei = new(); - public List perceptei = new(); - public List receptors = new(); - - // This is probably always the first element in the nuclei list... - [System.NonSerialized] - public Nucleus root; - public int rootId; - - public NanoBrain() { - this.root = new Neuroid(this, "Root"); - } - - public Neuroid AddNeuron(string name) { - Neuroid neuroid = new(this, name); - return neuroid; - } - - public void UpdateNuclei() { - foreach (Nucleus nucleus in nuclei) - nucleus.IncreaseAge(); - foreach (Perceptoid perception in perceptei) - perception.IncreaseAge(); - } - - public void OnBeforeSerialize() { - this.rootId = root.id; - } - public void OnAfterDeserialize() { - try { - foreach (Nucleus nucleus in this.nuclei.ToArray()) { - if (this.rootId == nucleus.id) - this.root = nucleus; - nucleus.Rebuild(this); - } - - foreach (Perceptoid perceptoid in this.perceptei.ToArray()) - perceptoid.Rebuild(this); - } - catch (System.Exception) { } - this.GarbageCollection(); - } - - public void GarbageCollection() { - HashSet visitedNuclei = new(); - MarkNuclei(visitedNuclei, this.root); - //Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei"); - this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false); - this.perceptei.RemoveAll(perceptoid => visitedNuclei.Contains(perceptoid) == false); - } - - public void MarkNuclei(HashSet visitedNuclei, Nucleus nucleus) { - if (nucleus is null) - return; - - if (nucleus.brain == null) - nucleus.brain = this; - - visitedNuclei.Add(nucleus); - if (nucleus.synapses != null) { - HashSet visitedSynapses = new(); - foreach (Synapse synapse in nucleus.synapses) { - if (synapse != null && synapse.nucleus != null) { - visitedSynapses.Add(synapse); - MarkNuclei(visitedNuclei, synapse.nucleus); - } - } - nucleus.synapses.RemoveAll(synapse => visitedSynapses.Contains(synapse) == false); - } - if (nucleus.receivers != null) { - HashSet visitedReceivers = new(); - foreach (Receiver receiver in nucleus.receivers) { - if (receiver != null && receiver.nucleus != null) { - visitedReceivers.Add(receiver); - visitedNuclei.Add(receiver.nucleus); - } - } - nucleus.receivers.RemoveAll(receiver => visitedReceivers.Contains(receiver) == false); - } - } +using System.Collections.Generic; +using UnityEngine; + +[CreateAssetMenu(menuName = "Passer/NanoBrain")] +public class NanoBrain : ScriptableObject, ISerializationCallbackReceiver { + public List nuclei = new(); + public List perceptei = new(); + public List receptors = new(); + + public Cluster cluster; + + // This is probably always the first element in the nuclei list... + [System.NonSerialized] + public Nucleus output; + public int rootId; + + public NanoBrain() { + this.output = new Neuroid(this.cluster, "Root"); + this.cluster = new(); + } + + public void AddReceiver(INucleus receiver) { + output.AddReceiver(receiver); + } + + public Neuroid AddNeuron(string name) { + Neuroid neuroid = new(this.cluster, name); + return neuroid; + } + + public void UpdateNuclei() { + foreach (Nucleus nucleus in nuclei) + nucleus.IncreaseAge(); + foreach (Perceptoid perception in perceptei) + perception.IncreaseAge(); + } + + public void OnBeforeSerialize() { + this.rootId = output.id; + } + public void OnAfterDeserialize() { + try { + foreach (Nucleus nucleus in this.nuclei.ToArray()) { + if (this.rootId == nucleus.id) + this.output = nucleus; + nucleus.Rebuild(this); + } + + foreach (Perceptoid perceptoid in this.perceptei.ToArray()) + perceptoid.Rebuild(this); + } + catch (System.Exception) { } + this.cluster.GarbageCollection(); + } + +/* + public void GarbageCollection() { + HashSet visitedNuclei = new(); + MarkNuclei(visitedNuclei, this.output); + //Debug.Log($"Garbage collection found {visitedNuclei.Count} Nuclei"); + this.nuclei.RemoveAll(nucleus => visitedNuclei.Contains(nucleus) == false); + this.perceptei.RemoveAll(perceptoid => visitedNuclei.Contains(perceptoid) == false); + } + + public void MarkNuclei(HashSet visitedNuclei, INucleus nucleus) { + if (nucleus is null) + return; + + if (nucleus.brain == null) + nucleus.brain = this; + + visitedNuclei.Add(nucleus); + if (nucleus.synapses != null) { + HashSet visitedSynapses = new(); + foreach (Synapse synapse in nucleus.synapses) { + if (synapse != null && synapse.nucleus != null) { + visitedSynapses.Add(synapse); + MarkNuclei(visitedNuclei, synapse.nucleus); + } + } + nucleus.synapses.RemoveAll(synapse => visitedSynapses.Contains(synapse) == false); + } + if (nucleus.receivers != null) { + HashSet visitedReceivers = new(); + foreach (Receiver receiver in nucleus.receivers) { + if (receiver != null && receiver.nucleus != null) { + visitedReceivers.Add(receiver); + visitedNuclei.Add(receiver.nucleus); + } + } + nucleus.receivers.RemoveAll(receiver => visitedReceivers.Contains(receiver) == false); + } + } + */ } \ No newline at end of file diff --git a/Assets/NanoBrain/VisualEditor/NanoBrainComponent.cs b/Assets/NanoBrain/VisualEditor/NanoBrainComponent.cs index bdeda86..5be46f1 100644 --- a/Assets/NanoBrain/VisualEditor/NanoBrainComponent.cs +++ b/Assets/NanoBrain/VisualEditor/NanoBrainComponent.cs @@ -1,33 +1,33 @@ -using UnityEngine; - -public class NanoBrainComponent : MonoBehaviour { - public NanoBrain defaultBrain; - private NanoBrain brainInstance; - - public Nucleus root => brainInstance.root; - public NanoBrain brain { - get { - if (brainInstance == null && defaultBrain != null) { - brainInstance = Instantiate(defaultBrain); - brainInstance.name = defaultBrain.name + " (Instance)"; - - SwarmControl sc = FindFirstObjectByType(); - UpdateWeight(brainInstance, "Avoidance", sc.avoidanceForce); - UpdateWeight(brainInstance, "Cohesion", sc.cohesionForce); - UpdateWeight(brainInstance, "Separation", sc.separationForce); - UpdateWeight(brainInstance, "Alignment", sc.alignmentForce); - } - return brainInstance; - } - } - - public static void UpdateWeight(NanoBrain brain, string name, float weight) { - Nucleus root = brain.root; - foreach (Synapse synapse in root.synapses) { - if (synapse.nucleus.name == name) { - synapse.weight = weight; - } - } - } - +using UnityEngine; + +public class NanoBrainComponent : MonoBehaviour { + public NanoBrain defaultBrain; + private NanoBrain brainInstance; + + public Nucleus root => brainInstance.output; + public NanoBrain brain { + get { + if (brainInstance == null && defaultBrain != null) { + brainInstance = Instantiate(defaultBrain); + brainInstance.name = defaultBrain.name + " (Instance)"; + + SwarmControl sc = FindFirstObjectByType(); + UpdateWeight(brainInstance, "Avoidance", sc.avoidanceForce); + UpdateWeight(brainInstance, "Cohesion", sc.cohesionForce); + UpdateWeight(brainInstance, "Separation", sc.separationForce); + UpdateWeight(brainInstance, "Alignment", sc.alignmentForce); + } + return brainInstance; + } + } + + public static void UpdateWeight(NanoBrain brain, string name, float weight) { + Nucleus root = brain.output; + foreach (Synapse synapse in root.synapses) { + if (synapse.nucleus.name == name) { + synapse.weight = weight; + } + } + } + } \ No newline at end of file diff --git a/Assets/Scenes/Boids/New Cluster 1.asset b/Assets/Scenes/Boids/New Cluster 1.asset new file mode 100644 index 0000000..c46baec --- /dev/null +++ b/Assets/Scenes/Boids/New Cluster 1.asset @@ -0,0 +1,20 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 60a957541c24c57e78018c202ebb1d9b, type: 3} + m_Name: New Cluster 1 + m_EditorClassIdentifier: Assembly-CSharp::Cluster + nuclei: + - id: 949579472 + _name: Output + _synapses: [] + _receivers: [] + nucleusType: diff --git a/Assets/Scenes/Boids/Identity.asset.meta b/Assets/Scenes/Boids/New Cluster 1.asset.meta similarity index 79% rename from Assets/Scenes/Boids/Identity.asset.meta rename to Assets/Scenes/Boids/New Cluster 1.asset.meta index 9797408..fcb368b 100644 --- a/Assets/Scenes/Boids/Identity.asset.meta +++ b/Assets/Scenes/Boids/New Cluster 1.asset.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 5040f18b0515ba23eb0782d6f6794054 +guid: ad89de17be687dbc18a57252cadda0f3 NativeFormatImporter: externalObjects: {} mainObjectFileID: 11400000 diff --git a/Assets/Scenes/Boids/New Cluster.asset b/Assets/Scenes/Boids/New Cluster.asset new file mode 100644 index 0000000..fe0b8a4 --- /dev/null +++ b/Assets/Scenes/Boids/New Cluster.asset @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 60a957541c24c57e78018c202ebb1d9b, type: 3} + m_Name: New Cluster + m_EditorClassIdentifier: Assembly-CSharp::Cluster + nuclei: [] diff --git a/Assets/Scenes/Boids/New Cluster.asset.meta b/Assets/Scenes/Boids/New Cluster.asset.meta new file mode 100644 index 0000000..575238a --- /dev/null +++ b/Assets/Scenes/Boids/New Cluster.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eddc759ede59e66cd936ad6ae2c55c46 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/Boids/Identity.asset b/Assets/Scenes/Boids/New Nano Brain.asset similarity index 94% rename from Assets/Scenes/Boids/Identity.asset rename to Assets/Scenes/Boids/New Nano Brain.asset index 94f63d4..5251743 100644 --- a/Assets/Scenes/Boids/Identity.asset +++ b/Assets/Scenes/Boids/New Nano Brain.asset @@ -10,14 +10,14 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 36081359186edfec998d891a1feeb17b, type: 3} - m_Name: Identity + m_Name: New Nano Brain m_EditorClassIdentifier: Assembly-CSharp::NanoBrain title: count: 0 color: {r: 1, g: 1, b: 1, a: 1} texture: {fileID: 0} nuclei: - - id: 1707104464 + - id: 2025140912 _name: Root synapses: [] receivers: [] @@ -53,7 +53,7 @@ MonoBehaviour: inverse: 0 exponent: 1 perceptei: [] - rootId: 1707104464 + rootId: 2025140912 references: version: 2 RefIds: [] diff --git a/Assets/Scenes/Boids/New Nano Brain.asset.meta b/Assets/Scenes/Boids/New Nano Brain.asset.meta new file mode 100644 index 0000000..cc404a1 --- /dev/null +++ b/Assets/Scenes/Boids/New Nano Brain.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fab876d6bf7dc9b10a56541a7eeccdd2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: